Conflicts:
	docs/public/02-architecture.asciidoc
	docs/public/04-authorization.asciidoc
	docs/public/07-securing-nodes.asciidoc
	docs/public/clients/kibana.asciidoc
	docs/public/clients/logstash.asciidoc
	docs/public/clients/marvel.asciidoc

Original commit: elastic/x-pack-elasticsearch@57efef1bf6
This commit is contained in:
Paul Echeverri 2014-10-27 11:16:58 -07:00
commit 647e545c79
8 changed files with 322 additions and 22 deletions

View File

@ -11,16 +11,19 @@ import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.cli.commons.CommandLine;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.collect.ObjectArrays;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.collect.*;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.esusers.FileUserPasswdStore;
import org.elasticsearch.shield.authc.esusers.FileUserRolesStore;
import org.elasticsearch.shield.authc.support.Hasher;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.authz.AuthorizationService;
import org.elasticsearch.shield.authz.Permission;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.transport.TransportRequest;
import java.nio.file.Files;
import java.nio.file.Path;
@ -118,6 +121,7 @@ public class ESUsersTool extends CliTool {
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
verifyRoles(terminal, settings, env, roles);
Path file = FileUserPasswdStore.resolveFile(settings, env);
Map<String, char[]> users = new HashMap<>(FileUserPasswdStore.parseFile(file, null));
if (users.containsKey(username)) {
@ -168,8 +172,6 @@ public class ESUsersTool extends CliTool {
char[] passwd = users.remove(username);
if (passwd != null) {
FileUserPasswdStore.writeFile(users, file);
} else {
terminal.println("Warning: users file [%s] did not contain password entry for user [%s]", file.toAbsolutePath(), username);
}
}
@ -179,8 +181,6 @@ public class ESUsersTool extends CliTool {
String[] roles = userRoles.remove(username);
if (roles != null) {
FileUserRolesStore.writeFile(userRoles, file);
} else {
terminal.println("Warning: users_roles file [%s] did not contain roles entry for user [%s]", file.toAbsolutePath(), username);
}
}
@ -311,6 +311,7 @@ public class ESUsersTool extends CliTool {
if (userRoles.get(username) != null) {
roles.addAll(Arrays.asList(userRoles.get(username)));
}
verifyRoles(terminal, settings, env, addRoles);
roles.addAll(Arrays.asList(addRoles));
roles.removeAll(Arrays.asList(removeRoles));
@ -347,6 +348,7 @@ public class ESUsersTool extends CliTool {
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
ImmutableMap<String, Permission.Global> knownRoles = loadRoles(terminal, settings, env);
Path userRolesFilePath = FileUserRolesStore.resolveFile(settings, env);
Map<String, String[]> userRoles = FileUserRolesStore.parseFile(userRolesFilePath, null);
Path userFilePath = FileUserPasswdStore.resolveFile(settings, env);
@ -359,13 +361,27 @@ public class ESUsersTool extends CliTool {
}
if (userRoles.containsKey(username)) {
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(userRoles.get(username)));
String[] roles = userRoles.get(username);
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
terminal.println("%-15s: %s", username, Joiner.on(",").useForNull("-").join(markedRoles));
if (!unknownRoles.isEmpty()) {
// at least one role is marked... so printing the legend
Path rolesFile = FileRolesStore.resolveFile(settings, env).toAbsolutePath();
terminal.println();
terminal.println(" [*] An unknown role. Please check [%s] to see available roles", rolesFile.toAbsolutePath());
}
} else {
terminal.println("%-15s: -", username);
}
} else {
boolean unknownRolesFound = false;
for (Map.Entry<String, String[]> entry : userRoles.entrySet()) {
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(entry.getValue()));
String[] roles = entry.getValue();
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
String[] markedRoles = markUnknownRoles(roles, unknownRoles);
terminal.println("%-15s: %s", entry.getKey(), Joiner.on(",").join(markedRoles));
unknownRolesFound = unknownRolesFound || !unknownRoles.isEmpty();
}
// list users without roles
Set<String> usersWithoutRoles = Sets.newHashSet(users);
@ -374,9 +390,67 @@ public class ESUsersTool extends CliTool {
terminal.println("%-15s: -", user);
}
}
if (unknownRolesFound) {
// at least one role is marked... so printing the legend
Path rolesFile = FileRolesStore.resolveFile(settings, env).toAbsolutePath();
terminal.println();
terminal.println(" [*] An unknown role. Please check [%s] to see available roles", rolesFile.toAbsolutePath());
}
}
return ExitStatus.OK;
}
}
private static ImmutableMap<String, Permission.Global> loadRoles(Terminal terminal, Settings settings, Environment env) {
Path rolesFile = FileRolesStore.resolveFile(settings, env);
try {
return FileRolesStore.parseFile(rolesFile, null, new DummyAuthzService());
} catch (Throwable t) {
// if for some reason, parsing fails (malformatted perhaps) we just warn
terminal.println("Warning: Could not parse [%s] for roles verification. Please revise and fix it. Nonetheless, the user will still be associated with all specified roles", rolesFile.toAbsolutePath());
}
return null;
}
private static String[] markUnknownRoles(String[] roles, Set<String> unknownRoles) {
if (unknownRoles.isEmpty()) {
return roles;
}
String[] marked = new String[roles.length];
for (int i = 0; i < roles.length; i++) {
if (unknownRoles.contains(roles[i])) {
marked[i] = roles[i] + "*";
} else {
marked[i] = roles[i];
}
}
return marked;
}
private static void verifyRoles(Terminal terminal, Settings settings, Environment env, String[] roles) {
ImmutableMap<String, Permission.Global> knownRoles = loadRoles(terminal, settings, env);
if (knownRoles == null) {
return;
}
Set<String> unknownRoles = Sets.difference(Sets.newHashSet(roles), knownRoles.keySet());
if (!unknownRoles.isEmpty()) {
Path rolesFile = FileRolesStore.resolveFile(settings, env);
terminal.println("Warning: The following roles [%s] are unknown. Make sure to add them to the [%s] file. " +
"Nonetheless the user will still be associated with all specified roles",
Strings.collectionToCommaDelimitedString(unknownRoles), rolesFile.toAbsolutePath());
}
}
private static class DummyAuthzService implements AuthorizationService {
@Override
public ImmutableList<String> authorizedIndicesAndAliases(User user, String action) {
return ImmutableList.of();
}
@Override
public void authorize(User user, String action, TransportRequest request) throws AuthorizationException {
}
}
}

View File

@ -46,6 +46,9 @@ public interface Permission {
static class Global implements Permission {
final static Predicate<String> clusterActionMatcher = Privilege.Cluster.ALL.predicate();
final static Predicate<String> indicesActionMatcher = Privilege.Index.ALL.predicate();
private final Cluster cluster;
private final Indices indices;
@ -67,11 +70,11 @@ public interface Permission {
}
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
if (cluster != null && cluster.check(user, action, request, metaData)) {
return true;
if (clusterActionMatcher.apply(action)) {
return cluster != null && cluster.check(user, action, request, metaData);
}
if (indices != null && indices.check(user, action, request, metaData)) {
return true;
if (indicesActionMatcher.apply(action)) {
return indices != null && indices.check(user, action, request, metaData);
}
return false;
}

View File

@ -59,7 +59,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, AuthorizationService authzService, Listener listener) {
super(settings);
file = resolveFile(componentSettings, env);
file = resolveFile(settings, env);
permissions = parseFile(file, logger, authzService);
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
watcher.addListener(new FileListener(authzService));
@ -73,7 +73,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
}
public static Path resolveFile(Settings settings, Environment env) {
String location = settings.get("files.roles");
String location = settings.get("shield.authz.store.files.roles");
if (location == null) {
return ShieldPlugin.resolveConfigFile(env, "roles.yml");
}

View File

@ -5,11 +5,17 @@
*/
package org.elasticsearch.shield.plugin;
import org.elasticsearch.client.support.Headers;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.shield.ShieldModule;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import java.io.File;
import java.nio.file.Path;
@ -22,6 +28,12 @@ public class ShieldPlugin extends AbstractPlugin {
public static final String NAME = "shield";
private final Settings settings;
public ShieldPlugin(Settings settings) {
this.settings = settings;
}
@Override
public String name() {
return NAME;
@ -37,6 +49,26 @@ public class ShieldPlugin extends AbstractPlugin {
return ImmutableList.<Class<? extends Module>>of(ShieldModule.class);
}
@Override
public Settings additionalSettings() {
String setting = Headers.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER;
if (settings.get(setting) != null) {
return ImmutableSettings.EMPTY;
}
String user = settings.get("shield.user");
if (user == null) {
return ImmutableSettings.EMPTY;
}
int i = user.indexOf(":");
if (i < 0 || i == user.length() - 1) {
throw new ShieldSettingsException("Invalid [shield.user] settings. Must be in the form of \"<username>:<password>\"");
}
String username = user.substring(0, i);
String password = user.substring(i + 1);
return ImmutableSettings.builder()
.put(setting, UsernamePasswordToken.basicAuthHeaderValue(username, new SecuredString(password.toCharArray()))).build();
}
public static Path configDir(Environment env) {
return new File(env.configFile(), NAME).toPath();
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.shield;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.shield.authz.AuthorizationException;
@ -94,5 +95,20 @@ public class MultipleIndicesPermissionsTests extends ShieldIntegrationTest {
} catch (AuthorizationException ae) {
// expected
}
MultiSearchResponse msearchResponse = client.prepareMultiSearch()
.add(client.prepareSearch("test"))
.add(client.prepareSearch("test1"))
.get();
MultiSearchResponse.Item[] items = msearchResponse.getResponses();
assertThat(items.length, is(2));
assertThat(items[0].isFailure(), is(false));
searchResponse = items[0].getResponse();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 1);
assertThat(items[1].isFailure(), is(false));
searchResponse = items[1].getResponse();
assertNoFailures(searchResponse);
assertHitCount(searchResponse, 1);
}
}

View File

@ -0,0 +1,126 @@
/*
* 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.
*/
package org.elasticsearch.shield;
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authz.AuthorizationException;
import org.elasticsearch.shield.test.ShieldIntegrationTest;
import org.junit.Test;
import java.util.List;
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.hasSize;
/**
* This test makes sure that if an action is a cluster action (according to our
* internal categorization in shield, then we apply the cluster priv checks and don't
* fallback on the indices privs at all. In particular, this is useful when we want to treat
* actions that are normally categorized as index actions as cluster actions - for example,
* index template actions.
*/
public class PermissionPrecedenceTests extends ShieldIntegrationTest {
static final String ROLES =
"admin:\n" +
" cluster: all\n" +
" indices:\n" +
" '*': all\n" +
"\n" +
"user:\n" +
" indices:\n" +
" 'test_*': all\n";
static final String USERS =
"admin:{plain}test123\n" +
"user:{plain}test123\n";
static final String USERS_ROLES =
"admin:admin\n" +
"user:user\n";
@Override
protected String configRole() {
return ROLES;
}
@Override
protected String configUsers() {
return USERS;
}
@Override
protected String configUsersRoles() {
return USERS_ROLES;
}
@Override
protected String getClientUsername() {
return "admin";
}
@Override
protected SecuredString getClientPassword() {
return SecuredStringTests.build("test123");
}
@Test
public void testDifferetCombinationsOfIndices() throws Exception {
ClusterService clusterService = internalCluster().getInstance(ClusterService.class);
TransportAddress address = clusterService.localNode().address();
TransportClient client = new TransportClient(ImmutableSettings.builder()
.put("cluster.name", internalCluster().getClusterName())
.put("node.mode", "network")
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")))
.addTransportAddress(address);
// first lets try with "admin"... all should work
PutIndexTemplateResponse putResponse = client.admin().indices().preparePutTemplate("template1")
.setTemplate("test_*")
.putHeader("Authorization", basicAuthHeaderValue("admin", SecuredStringTests.build("test123")))
.get();
assertAcked(putResponse);
GetIndexTemplatesResponse getResponse = client.admin().indices().prepareGetTemplates("template1")
.putHeader("Authorization", basicAuthHeaderValue("admin", SecuredStringTests.build("test123")))
.get();
List<IndexTemplateMetaData> templates = getResponse.getIndexTemplates();
assertThat(templates, hasSize(1));
// now lets try with "user"
try {
client.admin().indices().preparePutTemplate("template1")
.setTemplate("test_*")
.putHeader("Authorization", basicAuthHeaderValue("user", SecuredStringTests.build("test123")))
.get();
fail("expected an authorization exception as template APIs should require cluster ALL permission");
} catch (AuthorizationException ae) {
// expected;
}
try {
client.admin().indices().prepareGetTemplates("template1")
.putHeader("Authorization", basicAuthHeaderValue("user", SecuredStringTests.build("test123")))
.get();
fail("expected an authorization exception as template APIs should require cluster ALL permission");
} catch (AuthorizationException ae) {
// expected
}
}
}

View File

@ -478,9 +478,11 @@ public class ESUsersToolTests extends CliToolTestCase {
public void testRoles_Cmd_testNotAddingOrRemovingRolesShowsListingOfRoles() throws Exception {
File usersFile = writeFile("admin:hash\nuser:hash");
File usersRoleFile = writeFile("admin: admin\nuser:user\nfoo:user\nbar:user\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();
@ -495,9 +497,11 @@ public class ESUsersToolTests extends CliToolTestCase {
public void testRoles_cmd_testRoleCanBeAddedWhenUserIsNotInRolesFile() throws Exception {
File usersFile = writeFile("admin:hash\nuser:hash");
File usersRoleFile = writeFile("admin: admin\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nmyrole:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();
@ -523,8 +527,10 @@ public class ESUsersToolTests extends CliToolTestCase {
@Test
public void testListUsersAndRoles_Cmd_listAllUsers() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();
@ -537,13 +543,34 @@ public class ESUsersToolTests extends CliToolTestCase {
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo,bar"))));
}
@Test
public void testListUsersAndRoles_Cmd_listAllUsers_WithUnknownRoles() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();
ESUsersTool.ListUsersAndRoles cmd = new ESUsersTool.ListUsersAndRoles(catchTerminalOutput, null);
CliTool.ExitStatus status = execute(cmd, settings);
assertThat(status, is(CliTool.ExitStatus.OK));
assertThat(catchTerminalOutput.getTerminalOutput(), hasSize(greaterThanOrEqualTo(2)));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(containsString("admin")));
assertThat(catchTerminalOutput.getTerminalOutput(), hasItem(allOf(containsString("user"), containsString("user,foo*,bar*"))));
}
@Test
public void testListUsersAndRoles_Cmd_listSingleUser() throws Exception {
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();
@ -574,9 +601,11 @@ public class ESUsersToolTests extends CliToolTestCase {
public void testListUsersAndRoles_Cmd_testThatUsersWithoutRolesAreListed() throws Exception {
File usersFile = writeFile("admin:{plain}changeme\nuser:{plain}changeme\nno-roles-user:{plain}changeme\n");
File usersRoleFile = writeFile("admin: admin\nuser: user\nfoo:user\nbar:user\n");
File rolesFile = writeFile("admin:\n cluster: all\n\nuser:\n cluster: all\n\nfoo:\n cluster: all\n\nbar:\n cluster: all");
Settings settings = ImmutableSettings.builder()
.put("shield.authc.esusers.files.users_roles", usersRoleFile)
.put("shield.authc.esusers.files.users", usersFile)
.put("shield.authz.store.files.roles", rolesFile)
.build();
CaptureOutputTerminal catchTerminalOutput = new CaptureOutputTerminal();

View File

@ -63,19 +63,20 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
File folder = newFolder();
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put("discovery.zen.ping.multicast.enabled", false)
.put("discovery.type", "zen")
.put("node.mode", "network")
.put("plugin.types", ShieldPlugin.class.getName())
.put("shield.authc.esusers.files.users", writeFile(folder, "users", CONFIG_STANDARD_USER))
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES))
.put("shield.authc.esusers.files.users", writeFile(folder, "users", configUsers()))
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", configUsersRoles()))
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", configRole()))
.put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL))
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode"))
.put("shield.audit.enabled", true)
.put("plugins.load_classpath_plugins", false);
setUser(builder);
if (OsUtils.MAC) {
builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1");
}
@ -87,15 +88,34 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
return CONFIG_ROLE_ALLOW_ALL;
}
protected String configUsers() {
return CONFIG_STANDARD_USER;
}
protected String configUsersRoles() {
return CONFIG_STANDARD_USER_ROLES;
}
@Override
protected Settings transportClientSettings() {
return ImmutableSettings.builder()
ImmutableSettings.Builder builder = ImmutableSettings.builder()
.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()))
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName())
.put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false)
.put("node.mode", "network")
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient"))
.build();
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient"));
setUser(builder);
return builder.build();
}
protected void setUser(ImmutableSettings.Builder settings) {
if (randomBoolean()) {
settings.put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword()));
} else {
settings.put("shield.user", getClientUsername() + ":" + new String(getClientPassword().internalChars()));
}
}
protected String writeFile(File folder, String name, String content) {