Skip to content

Commit

Permalink
Remove password checks in REST API calls. Or replace them by the memb…
Browse files Browse the repository at this point in the history
…er JAAS authentication if Enterprise is used and Hazelcast Security is enabled.
  • Loading branch information
kwart committed Jan 16, 2019
1 parent 781f967 commit aec2e8e
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,14 @@
<xs:complexType name="cluster-group">
<xs:all>
<xs:element name="name" type="xs:string" minOccurs="0" maxOccurs="1" default="dev"/>
<xs:element name="password" type="xs:string" minOccurs="0" maxOccurs="1" default="dev-pass"/>
<xs:element name="password" type="xs:string" minOccurs="0" maxOccurs="1" default="dev-pass">
<xs:annotation>
<xs:documentation>
Password of the group to connect to.
The password is only checked when security is enabled on Hazelcast members.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
</xs:complexType>
<xs:complexType name="listeners">
Expand Down
8 changes: 4 additions & 4 deletions hazelcast/src/main/java/com/hazelcast/config/GroupConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ public GroupConfig setName(final String name) {
* Gets the password of the group.
*
* @return the password of the group
* @deprecated since 3.11, password check is removed.
* use {@link SecurityConfig()} , ClientSecurityConfig() for authentication
* @deprecated since 3.11, password check is removed. Passwords are only checked in default LoginModule when Hazelcast
* {@link SecurityConfig security} is enabled (Enterprise edition only).
*/
@Deprecated
public String getPassword() {
Expand All @@ -105,8 +105,8 @@ public String getPassword() {
* @param password the password to set for the group
* @return the updated GroupConfig
* @throws IllegalArgumentException if password is {@code null}
* @deprecated since 3.11, password check is removed.
* use {@link SecurityConfig()} , ClientSecurityConfig() for authentication
* @deprecated since 3.11, password check is removed. Passwords are only checked in default LoginModule when Hazelcast
* {@link SecurityConfig security} is enabled (Enterprise edition only).
*/
@Deprecated
public GroupConfig setPassword(final String password) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public abstract class HttpCommand extends AbstractTextCommand {
public static final String HEADER_CUSTOM_PREFIX = "Hazelcast-";
public static final byte[] RES_200 = stringToBytes("HTTP/1.1 200 OK\r\n");
public static final byte[] RES_400 = stringToBytes("HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n");
public static final byte[] RES_403 = stringToBytes("HTTP/1.1 403 Forbidden\r\n\r\n");
public static final byte[] RES_403 = stringToBytes("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n");
public static final byte[] RES_404 = stringToBytes("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n");
public static final byte[] RES_100 = stringToBytes("HTTP/1.1 100 Continue\r\n\r\n");
public static final byte[] RES_204 = stringToBytes("HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@
import com.hazelcast.internal.json.Json;
import com.hazelcast.internal.management.ManagementCenterService;
import com.hazelcast.internal.management.dto.WanReplicationConfigDTO;
import com.hazelcast.internal.management.request.UpdatePermissionConfigRequest;
import com.hazelcast.logging.ILogger;
import com.hazelcast.security.SecurityService;
import com.hazelcast.security.SecurityContext;
import com.hazelcast.security.UsernamePasswordCredentials;
import com.hazelcast.spi.properties.GroupProperty;
import com.hazelcast.spi.properties.HazelcastProperties;
import com.hazelcast.util.JsonUtil;
import com.hazelcast.util.StringUtil;
import com.hazelcast.version.Version;
Expand All @@ -38,6 +39,9 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import static com.hazelcast.util.StringUtil.bytesToString;
import static com.hazelcast.util.StringUtil.lowerCaseInternal;
import static com.hazelcast.util.StringUtil.stringToBytes;
Expand Down Expand Up @@ -117,24 +121,21 @@ public void handle(HttpPostCommand command) {
private void handleChangeClusterState(HttpPostCommand command) throws UnsupportedEncodingException {
byte[] data = command.getData();
String[] strList = bytesToString(data).split("&");
String groupName = URLDecoder.decode(strList[0], "UTF-8");
String groupPass = URLDecoder.decode(strList[1], "UTF-8");
String stateParam = URLDecoder.decode(strList[2], "UTF-8");
String res;
try {
Node node = textCommandService.getNode();
ClusterService clusterService = node.getClusterService();
GroupConfig groupConfig = node.getConfig().getGroupConfig();
if (!(groupConfig.getName().equals(groupName) && groupConfig.getPassword().equals(groupPass))) {
res = response(ResponseType.FORBIDDEN);
} else {
if (authenticate(command, strList[0], strList.length > 1 ? strList[1] : null)) {
String stateParam = URLDecoder.decode(strList[2], "UTF-8");
ClusterState state = ClusterState.valueOf(upperCaseInternal(stateParam));
if (!state.equals(clusterService.getClusterState())) {
clusterService.changeClusterState(state);
res = response(ResponseType.SUCCESS, "state", state.toString().toLowerCase(StringUtil.LOCALE_INTERNAL));
} else {
res = response(ResponseType.FAIL, "state", state.toString().toLowerCase(StringUtil.LOCALE_INTERNAL));
}
} else {
res = response(ResponseType.FORBIDDEN);
}
} catch (Throwable throwable) {
logger.warning("Error occurred while changing cluster state", throwable);
Expand Down Expand Up @@ -164,20 +165,17 @@ private void handleGetClusterState(HttpPostCommand command) throws UnsupportedEn
private void handleChangeClusterVersion(HttpPostCommand command) throws UnsupportedEncodingException {
byte[] data = command.getData();
String[] strList = bytesToString(data).split("&");
String groupName = URLDecoder.decode(strList[0], "UTF-8");
String groupPass = URLDecoder.decode(strList[1], "UTF-8");
String versionParam = URLDecoder.decode(strList[2], "UTF-8");
String res;
try {
Node node = textCommandService.getNode();
ClusterService clusterService = node.getClusterService();
GroupConfig groupConfig = node.getConfig().getGroupConfig();
if (!(groupConfig.getName().equals(groupName) && groupConfig.getPassword().equals(groupPass))) {
res = response(ResponseType.FORBIDDEN);
} else {
if (authenticate(command, strList[0], strList.length > 1 ? strList[1] : null)) {
String versionParam = URLDecoder.decode(strList[2], "UTF-8");
Version version = Version.of(versionParam);
clusterService.changeClusterVersion(version);
res = response(ResponseType.SUCCESS, "version", clusterService.getClusterVersion().toString());
} else {
res = response(ResponseType.FORBIDDEN);
}
} catch (Throwable throwable) {
logger.warning("Error occurred while changing cluster version", throwable);
Expand Down Expand Up @@ -348,22 +346,29 @@ private void handleQueue(HttpPostCommand command, String uri) {
}

private void handleManagementCenterUrlChange(HttpPostCommand command) throws UnsupportedEncodingException {
if (textCommandService.getNode().getProperties().getBoolean(GroupProperty.MC_URL_CHANGE_ENABLED)) {
byte[] res = HttpCommand.RES_204;
byte[] data = command.getData();
String[] strList = bytesToString(data).split("&");
String cluster = URLDecoder.decode(strList[0], "UTF-8");
String pass = URLDecoder.decode(strList[1], "UTF-8");
String url = URLDecoder.decode(strList[2], "UTF-8");

HazelcastProperties properties = textCommandService.getNode().getProperties();
if (! properties.getBoolean(GroupProperty.MC_URL_CHANGE_ENABLED)) {
logger.warning("Hazelcast property " + GroupProperty.MC_URL_CHANGE_ENABLED.getName() + " is deprecated.");
command.setResponse(HttpCommand.RES_503);
return;
}
byte[] res;
String[] strList = bytesToString(command.getData()).split("&");
if (authenticate(command, strList[0], strList.length > 1 ? strList[1] : null)) {
ManagementCenterService managementCenterService = textCommandService.getNode().getManagementCenterService();
if (managementCenterService != null) {
res = managementCenterService.clusterWideUpdateManagementCenterUrl(cluster, pass, url);
String url = URLDecoder.decode(strList[2], "UTF-8");
res = managementCenterService.clusterWideUpdateManagementCenterUrl(url);
} else {
logger.warning(
"Unable to change URL of ManagementCenter as the ManagementCenterService is not running on this member.");
res = HttpCommand.RES_204;
}
command.setResponse(res);
} else {
command.setResponse(HttpCommand.RES_503);
res = HttpCommand.RES_403;
}

command.setResponse(res);
}

private void handleMap(HttpPostCommand command, String uri) {
Expand Down Expand Up @@ -589,41 +594,6 @@ private void handleUpdatePermissions(HttpPostCommand command) {
return;
}

// This is intentionally not used. Instead handleUpdatePermissions() returns FORBIDDEN always.
private void doHandleUpdatePermissions(HttpPostCommand command) throws UnsupportedEncodingException {

if (!checkCredentials(command)) {
String res = response(ResponseType.FORBIDDEN);
command.setResponse(HttpCommand.CONTENT_TYPE_JSON, stringToBytes(res));
return;
}

SecurityService securityService = textCommandService.getNode().getSecurityService();
if (securityService == null) {
String res = response(ResponseType.FAIL, "message", "Security features are only available on Hazelcast Enterprise!");
command.setResponse(HttpCommand.CONTENT_TYPE_JSON, stringToBytes(res));
return;
}

String res;
byte[] data = command.getData();
String[] strList = bytesToString(data).split("&");
//Start from 3rd item of strList as first two are used for credentials
String permConfigsJSON = URLDecoder.decode(strList[2], "UTF-8");

try {
UpdatePermissionConfigRequest request = new UpdatePermissionConfigRequest();
request.fromJson(Json.parse(permConfigsJSON).asObject());
securityService.refreshClientPermissions(request.getPermissionConfigs());
res = response(ResponseType.SUCCESS, "message", "Permissions updated.");
} catch (Exception ex) {
logger.warning("Error occurred while updating permission config", ex);
res = exceptionResponse(ex);
}

command.setResponse(HttpCommand.CONTENT_TYPE_JSON, stringToBytes(res));
}

private static String exceptionResponse(Throwable throwable) {
return response(ResponseType.FAIL, "message", throwable.getMessage());
}
Expand Down Expand Up @@ -674,14 +644,41 @@ private static String[] decodeParams(HttpPostCommand command, int paramCount) th

private boolean checkCredentials(HttpPostCommand command) throws UnsupportedEncodingException {
byte[] data = command.getData();
if (data == null) {
return false;
}
final String[] strList = bytesToString(data).split("&");
if (strList.length < 2) {
return authenticate(command, strList[0], strList.length > 1 ? strList[1] : null);
}

/**
* Checks if the request is valid. If Hazelcast Security is not enabled, then only the given group name is compared to
* configuration. Otherwise member JAAS authentication (member login module stack) is used to authenticate the command.
*/
private boolean authenticate(HttpPostCommand command, final String groupName, final String pass)
throws UnsupportedEncodingException {
String decodedName = URLDecoder.decode(groupName, "UTF-8");
SecurityContext securityContext = textCommandService.getNode().getNodeExtension().getSecurityContext();
if (securityContext == null) {
final GroupConfig groupConfig = textCommandService.getNode().getConfig().getGroupConfig();
if (pass != null && !pass.isEmpty()) {
logger.fine("Password was provided but the Hazelcast Security is disabled.");
}
return groupConfig.getName().equals(decodedName);
}
if (pass == null) {
logger.fine("Empty password is not allowed when the Hazelcast Security is enabled.");
return false;
}
String decodedPass = URLDecoder.decode(pass, "UTF-8");
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(groupName, decodedPass);
try {
LoginContext lc = securityContext.createMemberLoginContext(credentials);
lc.login();
} catch (LoginException e) {
return false;
}
final String groupName = URLDecoder.decode(strList[0], "UTF-8");
final String groupPass = URLDecoder.decode(strList[1], "UTF-8");
final GroupConfig groupConfig = textCommandService.getNode().getConfig().getGroupConfig();
return groupConfig.getName().equals(groupName) && groupConfig.getPassword().equals(groupPass);
return true;
}

private void sendResponse(HttpPostCommand command, String value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,8 @@ public void shutdown() {
}
}

public byte[] clusterWideUpdateManagementCenterUrl(String groupName, String groupPass, String newUrl) {
public byte[] clusterWideUpdateManagementCenterUrl(String newUrl) {
try {
GroupConfig groupConfig = instance.getConfig().getGroupConfig();
if (!(groupConfig.getName().equals(groupName) && groupConfig.getPassword().equals(groupPass))) {
return HttpCommand.RES_403;
}

final Collection<Member> memberList = instance.node.clusterService.getMembers();
for (Member member : memberList) {
send(member.getAddress(), new UpdateManagementCenterUrlOperation(newUrl));
Expand Down
1 change: 1 addition & 0 deletions hazelcast/src/main/resources/hazelcast-config-3.12.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,7 @@
<xs:annotation>
<xs:documentation>
Password of the group to be created.
The password is only checked when security is enabled on the member.
</xs:documentation>
</xs:annotation>
</xs:element>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ public int mapDelete(String mapName, String key) throws IOException {
return doDelete(url).responseCode;
}

public int shutdownCluster(String groupName, String groupPassword) throws IOException {
public ConnectionResponse shutdownCluster(String groupName, String groupPassword) throws IOException {
String url = address + "management/cluster/clusterShutdown";
return doPost(url, groupName, groupPassword).responseCode;
return doPost(url, groupName, groupPassword);
}

public String shutdownMember(String groupName, String groupPassword) throws IOException {
Expand Down

0 comments on commit aec2e8e

Please sign in to comment.