Skip to content

Commit

Permalink
GEODE-7156: add token based authentication support in management rest… (
Browse files Browse the repository at this point in the history
apache#4005)

* GEODE-7156: add token based authentication support in management rest api

Co-authored-by: Joris Melchior <[email protected]>

* added security-auth-token-enabled-components property
* pass this property to the management web application context
* enabled auth token filter when that property is set
* improve SimpleSecurityManager to authenticate mock token
  • Loading branch information
jinmeiliao authored Sep 6, 2019
1 parent ced3e45 commit 2e030f6
Show file tree
Hide file tree
Showing 28 changed files with 601 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.
*/

package org.apache.geode.rest.internal.web;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;

import org.junit.ClassRule;
import org.junit.Test;

import org.apache.geode.distributed.ConfigurationProperties;
import org.apache.geode.examples.SimpleSecurityManager;
import org.apache.geode.test.junit.rules.LocatorStarterRule;

public class ManagementRestAuthTokenIntegrationTest {

@ClassRule
public static LocatorStarterRule locator = new LocatorStarterRule()
.withProperty(ConfigurationProperties.SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS,
"all,management")
.withSecurityManager(SimpleSecurityManager.class)
.withHttpService()
.withAutoStart();

@Test
public void name() throws Exception {
String response =
requestUseBearerToken(
"http://localhost:" + locator.getHttpPort() + "/management/experimental/ping", "bar");

assertThat(response).isEqualTo("pong");
}

private static String requestUseBearerToken(String stringUrl, String bearerToken)
throws Exception {
BufferedReader reader = null;
URL url = new URL(stringUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", "Bearer " + bearerToken);
connection.setDoOutput(true);
connection.setRequestMethod("GET");
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
StringWriter out =
new StringWriter(connection.getContentLength() > 0 ? connection.getContentLength() : 2048);
while ((line = reader.readLine()) != null) {
out.append(line);
}
String response = out.toString();
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,7 @@ javadoc/org/apache/geode/redis/package-summary.html
javadoc/org/apache/geode/redis/package-tree.html
javadoc/org/apache/geode/security/AccessControl.html
javadoc/org/apache/geode/security/AuthInitialize.html
javadoc/org/apache/geode/security/AuthTokenEnabledComponents.html
javadoc/org/apache/geode/security/AuthenticationFailedException.html
javadoc/org/apache/geode/security/AuthenticationRequiredException.html
javadoc/org/apache/geode/security/Authenticator.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
import static org.apache.geode.distributed.ConfigurationProperties.MEMBERSHIP_PORT_RANGE;
import static org.apache.geode.distributed.ConfigurationProperties.MEMBER_TIMEOUT;
import static org.apache.geode.distributed.ConfigurationProperties.NAME;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS;
import static org.apache.geode.distributed.ConfigurationProperties.SERVER_SSL_ENABLED;
import static org.apache.geode.distributed.ConfigurationProperties.SSL_ENABLED_COMPONENTS;
import static org.apache.geode.distributed.ConfigurationProperties.START_LOCATOR;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_ARCHIVE_FILE;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLE_RATE;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLING_ENABLED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -677,6 +679,29 @@ public void testDeprecatedSSLProps() {
assertEquals(false, config1.sameAs(config3));
}

@Test
public void testEmptySecurityAuthTokenProp() throws Exception {
Properties props = getCommonProperties();
props.setProperty(SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS, "");
DistributionConfig config1 = new DistributionConfigImpl(props, false);
assertThat(config1.getSecurityAuthTokenEnabledComponents()).hasSize(0);
Properties securityProps = config1.getSecurityProps();
assertThat(securityProps.getProperty(SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS)).isEqualTo("");
assertThat(config1.getSecurityAuthTokenEnabledComponents()).hasSize(0);
}

@Test
public void testSecurityAuthTokenProp() throws Exception {
Properties props = getCommonProperties();
props.setProperty(SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS, "management");
DistributionConfig config1 = new DistributionConfigImpl(props, false);
assertThat(config1.getSecurityAuthTokenEnabledComponents()).containsExactly("MANAGEMENT");
Properties securityProps = config1.getSecurityProps();
assertThat(securityProps.getProperty(SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS))
.isEqualTo("management");
assertThat(config1.getSecurityAuthTokenEnabledComponents()).containsExactly("MANAGEMENT");
}

@Test
public void testSSLEnabledComponents() {
Properties props = getCommonProperties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2003,6 +2003,27 @@ public interface ConfigurationProperties {
* {@link org.apache.geode.security.SecurableCommunicationChannels} <U>Since</U>: Geode 1.0
*/
String SSL_ENABLED_COMPONENTS = "ssl-enabled-components";

/**
* The static String definition of the <i>"security-auth-token-enabled-components"</i> property <a
* name="security-auth-token-enabled-components"/a>
* </p>
* <U>Description</U>: This setting is a comma delimited list of component names which works in
* conjunction with
* the {@link #SECURITY_MANAGER} properties. if security manager is enabled, this property will
* determine what rest end point will use token based authentication instead of basic
* (username/password)
* authentication.
* </p>
* <U>Componant names</U>: "all","management" <U>Since</U>: Geode 1.11
* "all": shorthand for all the security components that support token authentication.
* "management": the {@link #ENABLE_MANAGEMENT_REST_SERVICE Management REST Service}
*
* Note: listing components that are not enabled does nothing.
*
* Default: empty. All security components use basic (username/password) authentication
*/
String SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS = SECURITY_PREFIX + "auth-token-enabled-components";
/**
* The static String definition of the <i>"ssl-ciphers"</i> property <a name="ssl-ciphers"/a>
* </p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import static org.apache.geode.distributed.ConfigurationProperties.REMOTE_LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.REMOVE_UNRESPONSIVE_CLIENT;
import static org.apache.geode.distributed.ConfigurationProperties.ROLES;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_ACCESSOR;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_ACCESSOR_PP;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTHENTICATOR;
Expand Down Expand Up @@ -756,7 +757,12 @@ public void setAttributeObject(String attName, Object attValue, ConfigSource sou
}

if (attName.startsWith(SECURITY_PREFIX)) {
setSecurity(attName, attValue.toString());
// some security properties will be an array, such as security-auth-token-enabled-components
if (attValue instanceof Object[]) {
setSecurity(attName, StringUtils.join((Object[]) attValue, ','));
} else {
setSecurity(attName, attValue.toString());
}
}

if (attName.startsWith(SSL_SYSTEM_PROPS_NAME) || attName.startsWith(SYS_PROP_NAME)) {
Expand Down Expand Up @@ -1218,6 +1224,9 @@ static Class _getAttributeType(String attName) {
m.put(SECURITY_PREFIX,
"Prefix for security related properties which are packed together and invoked as authentication parameter. Neither key nor value can be NULL. Legal tags can be [security-username, security-digitalid] and Legal values can be any string data.");

m.put(SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS,
"list of rest service to authenticate request with a Bearer token passed in the 'Authentication' header of the REST request. Otherwise BASIC authentication scheme is used. Possible value is a comma separated list of: 'all', 'management'. This property is ignored if 'security-manager' is not set. Default value is empty.");

m.put(USERDEFINED_PREFIX_NAME,
"Prefix for user defined properties which are used for replacements in Cache.xml. Neither key nor value can be NULL. Legal tags can be [custom-any-string] and Legal values can be any string data.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
import static org.apache.geode.distributed.ConfigurationProperties.REMOTE_LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.REMOVE_UNRESPONSIVE_CLIENT;
import static org.apache.geode.distributed.ConfigurationProperties.ROLES;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_ACCESSOR;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_ACCESSOR_PP;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTHENTICATOR;
Expand Down Expand Up @@ -2623,6 +2624,27 @@ public interface DistributionConfig extends Config, LogConfig, StatisticsConfig

String SECURITY_PREFIX_NAME = SECURITY_PREFIX;

/**
* Sets the value for
* {@link ConfigurationProperties#SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS}
*/
@ConfigAttributeSetter(name = SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS)
void setSecurityAuthTokenEnabledComponents(String[] newValue);

/**
* Returns the value of
* {@link ConfigurationProperties#SSECURITY_AUTH_TOKEN_ENABLED_COMPONENTS} property
*/
@ConfigAttributeGetter(name = SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS)
String[] getSecurityAuthTokenEnabledComponents();

/**
* the name of the {@link ConfigurationProperties#SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS}
* property
*/
@ConfigAttribute(type = String[].class)
String SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS_NAME =
SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS;

/**
* The static String definition of the cluster ssl prefix <i>"cluster-ssl"</i> used in conjunction
Expand Down Expand Up @@ -4996,6 +5018,9 @@ public interface DistributionConfig extends Config, LogConfig, StatisticsConfig
SecurableCommunicationChannel[] DEFAULT_SSL_ENABLED_COMPONENTS =
new SecurableCommunicationChannel[] {};

@Immutable
String[] DEFAULT_SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS = new String[0];

boolean DEFAULT_SSL_USE_DEFAULT_CONTEXT = false;

@ConfigAttribute(type = Boolean.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import org.apache.geode.internal.net.SocketCreator;
import org.apache.geode.internal.process.ProcessLauncherContext;
import org.apache.geode.internal.security.SecurableCommunicationChannel;
import org.apache.geode.security.AuthTokenEnabledComponents;

/**
* Provides an implementation of <code>DistributionConfig</code> that knows how to read the
Expand Down Expand Up @@ -605,6 +606,8 @@ public class DistributionConfigImpl extends AbstractDistributionConfig implement

private SecurableCommunicationChannel[] securableCommunicationChannels =
DEFAULT_SSL_ENABLED_COMPONENTS;
private String[] securityAuthTokenEnabledComponents =
DEFAULT_SECURITY_AUTH_TOKEN_ENABLED_COMPONENTS;

private boolean sslUseDefaultSSLContext = DEFAULT_SSL_USE_DEFAULT_CONTEXT;
private String sslProtocols = DEFAULT_SSL_PROTOCOLS;
Expand Down Expand Up @@ -866,8 +869,8 @@ public DistributionConfigImpl(DistributionConfig other) {
validateSerializableObjects = other.getValidateSerializableObjects();
serializableObjectFilter = other.getSerializableObjectFilter();

// following added for 9.9
enableManagementRestService = other.getEnableManagementRestService();
securityAuthTokenEnabledComponents = other.getSecurityAuthTokenEnabledComponents();
}

/**
Expand Down Expand Up @@ -2540,6 +2543,28 @@ public void setSecurity(String attName, String attValue) {
security.setProperty(attName, attValue);
}

@Override
public void setSecurityAuthTokenEnabledComponents(String[] newValue) {
// validate the value first
for (int i = 0; i < newValue.length; i++) {
String value = newValue[i];
try {
AuthTokenEnabledComponents.valueOf(value.toUpperCase());
// normalize the values to all uppercase
newValue[i] = value.toUpperCase();
} catch (Exception e) {
throw new IllegalArgumentException(
"Invalid security-auth-token-enabled-components value: " + value);
}
}
securityAuthTokenEnabledComponents = newValue;
}

@Override
public String[] getSecurityAuthTokenEnabledComponents() {
return securityAuthTokenEnabledComponents;
}

@Override
public boolean getRemoveUnresponsiveClient() {
return removeUnresponsiveClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -98,6 +99,7 @@
import org.apache.geode.management.internal.configuration.messages.ClusterManagementServiceInfoRequest;
import org.apache.geode.management.internal.configuration.messages.SharedConfigurationStatusRequest;
import org.apache.geode.management.internal.configuration.messages.SharedConfigurationStatusResponse;
import org.apache.geode.security.AuthTokenEnabledComponents;

/**
* Provides the implementation of a distribution {@code Locator} as well as internal-only
Expand Down Expand Up @@ -760,6 +762,12 @@ private void startClusterManagementService() throws IOException {
serviceAttributes.put(InternalHttpService.CLUSTER_MANAGEMENT_SERVICE_CONTEXT_PARAM,
clusterManagementService);

String[] authEnabledComponents = distributionConfig.getSecurityAuthTokenEnabledComponents();

boolean managementAuthTokenEnabled = Arrays.stream(authEnabledComponents)
.anyMatch(AuthTokenEnabledComponents::hasManagement);
serviceAttributes.put(InternalHttpService.AUTH_TOKEN_ENABLED_PARAM, managementAuthTokenEnabled);

if (distributionConfig.getEnableManagementRestService()) {
internalCache.getHttpService().ifPresent(x -> {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
*/
package org.apache.geode.examples;

import static org.apache.geode.security.SecurityManager.PASSWORD;
import static org.apache.geode.security.SecurityManager.TOKEN;
import static org.apache.geode.security.SecurityManager.USER_NAME;

import java.util.Properties;

import org.apache.geode.security.AuthenticationFailedException;
Expand All @@ -33,8 +37,12 @@ public void init(final Properties securityProps) {

@Override
public Object authenticate(final Properties credentials) throws AuthenticationFailedException {
String username = credentials.getProperty("security-username");
String password = credentials.getProperty("security-password");
String token = credentials.getProperty(TOKEN);
if (token != null) {
return "Bearer " + token;
}
String username = credentials.getProperty(USER_NAME);
String password = credentials.getProperty(PASSWORD);
if (username != null && username.equals(password)) {
return username;
}
Expand All @@ -43,6 +51,9 @@ public Object authenticate(final Properties credentials) throws AuthenticationFa

@Override
public boolean authorize(final Object principal, final ResourcePermission permission) {
if (principal.toString().startsWith("Bearer ")) {
return true;
}
String[] principals = principal.toString().toLowerCase().split(",");
for (String role : principals) {
String permissionString = permission.toString().replace(":", "").toLowerCase();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import java.util.StringTokenizer;
import java.util.TreeSet;

import org.apache.commons.lang3.StringUtils;

import org.apache.geode.InternalGemFireException;
import org.apache.geode.distributed.internal.FlowControlParams;
import org.apache.geode.internal.net.SocketCreator;
Expand Down Expand Up @@ -222,7 +224,12 @@ public void setAttribute(String name, String value, ConfigSource source) {
if (valueType.equals(String.class)) {
attObjectValue = value;
} else if (valueType.equals(String[].class)) {
attObjectValue = value.split(",");
// this would avoid converting empty string value to an array of size 1.
if (StringUtils.isBlank(value)) {
attObjectValue = new String[0];
} else {
attObjectValue = value.split(",");
}
} else if (valueType.equals(Integer.class)) {
attObjectValue = Integer.valueOf(value);
} else if (valueType.equals(Long.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

public class InternalHttpService implements HttpService {

public static final String AUTH_TOKEN_ENABLED_PARAM = "org.apache.geode.auth.token.enabled";
private static final Logger logger = LogService.getLogger();
private Server httpServer;
private String bindAddress = "0.0.0.0";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public class ResourceConstants {
"GemFire:service=AccessControl,type=Distributed";
public static final String USER_NAME = "security-username";
public static final String PASSWORD = "security-password";
public static final String TOKEN = "security-token";

public static final String MBEAN_TYPE_DISTRIBUTED = "Distributed";
public static final String MBEAN_TYPE_MEMBER = "Member";
Expand Down Expand Up @@ -147,6 +148,4 @@ public class ResourceConstants {
public static final String GETTER_STATUS = "status";

public static final String MANAGEMENT_PACKAGE = "org.apache.geode.management";


}
Loading

0 comments on commit 2e030f6

Please sign in to comment.