From 4dc237e721647707e612dee6331af241d08f6322 Mon Sep 17 00:00:00 2001 From: Zixuan Liu Date: Thu, 4 Aug 2022 18:47:13 +0800 Subject: [PATCH] [improve][authentication] Improve get the basic authentication config Signed-off-by: Zixuan Liu --- conf/broker.conf | 7 ++ conf/proxy.conf | 7 ++ conf/standalone.conf | 7 ++ .../AuthenticationProviderBasic.java | 67 ++++++++--- .../AuthenticationProviderBasicTest.java | 104 ++++++++++++++++++ .../resources/authentication/basic/.htpasswd | 2 + 6 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasicTest.java create mode 100644 pulsar-broker-common/src/test/resources/authentication/basic/.htpasswd diff --git a/conf/broker.conf b/conf/broker.conf index 0c554ada913472..bc26994db5b244 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -613,6 +613,13 @@ athenzDomainNames= # When this parameter is not empty, unauthenticated users perform as anonymousUserRole anonymousUserRole= +## Configure the datasource of basic authenticate, supports the file and Base64 format. +# file: +# basicAuthConf=/path/my/.htpasswd +# use Base64 to encode the contents of .htpasswd: +# basicAuthConf=YOUR-BASE64-DATA +basicAuthConf= + ### --- Token Authentication Provider --- ### ## Symmetric key diff --git a/conf/proxy.conf b/conf/proxy.conf index f0fa60483af2e5..b7292cec9faba7 100644 --- a/conf/proxy.conf +++ b/conf/proxy.conf @@ -201,6 +201,13 @@ httpRequestsLimitEnabled=false httpRequestsMaxPerSecond=100.0 +## Configure the datasource of basic authenticate, supports the file and Base64 format. +# file: +# basicAuthConf=/path/my/.htpasswd +# use Base64 to encode the contents of .htpasswd: +# basicAuthConf=YOUR-BASE64-DATA +basicAuthConf= + ### --- Token Authentication Provider --- ### ## Symmetric key diff --git a/conf/standalone.conf b/conf/standalone.conf index 2f38f0ebc03e6d..d53a3927a55c46 100644 --- a/conf/standalone.conf +++ b/conf/standalone.conf @@ -423,6 +423,13 @@ tokenAudienceClaim= # The token audience stands for this broker. The field `tokenAudienceClaim` of a valid token, need contains this. tokenAudience= +## Configure the datasource of basic authenticate, supports the file and Base64 format. +# file: +# basicAuthConf=/path/my/.htpasswd +# use Base64 to encode the contents of .htpasswd: +# basicAuthConf=YOUR-BASE64-DATA +basicAuthConf= + ### --- BookKeeper Client --- ### # Authentication plugin to use when connecting to bookies diff --git a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasic.java b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasic.java index 48d867f849c4fc..9f6bacf7298918 100644 --- a/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasic.java +++ b/pulsar-broker-common/src/main/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasic.java @@ -19,24 +19,32 @@ package org.apache.pulsar.broker.authentication; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.naming.AuthenticationException; +import lombok.Cleanup; import org.apache.commons.codec.digest.Crypt; import org.apache.commons.codec.digest.Md5Crypt; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.pulsar.broker.ServiceConfiguration; - -import lombok.Cleanup; import org.apache.pulsar.broker.authentication.metrics.AuthenticationMetrics; - -import javax.naming.AuthenticationException; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; +import org.apache.pulsar.client.api.url.URL; public class AuthenticationProviderBasic implements AuthenticationProvider { - private final static String HTTP_HEADER_NAME = "Authorization"; - private final static String CONF_SYSTEM_PROPERTY_KEY = "pulsar.auth.basic.conf"; + private static final String HTTP_HEADER_NAME = "Authorization"; + private static final String CONF_SYSTEM_PROPERTY_KEY = "pulsar.auth.basic.conf"; + private static final String CONF_PULSAR_PROPERTY_KEY = "basicAuthConf"; private Map users; @Override @@ -44,16 +52,38 @@ public void close() throws IOException { // noop } + public static byte[] readData(String data) + throws IOException, URISyntaxException, InstantiationException, IllegalAccessException { + if (data.startsWith("data:") || data.startsWith("file:")) { + return IOUtils.toByteArray(URL.createURL(data)); + } else if (Files.exists(Paths.get(data))) { + return Files.readAllBytes(Paths.get(data)); + } else if (org.apache.commons.codec.binary.Base64.isBase64(data)) { + return Base64.getDecoder().decode(data); + } else { + String msg = "Not supported config"; + throw new IllegalArgumentException(msg); + } + } + @Override public void initialize(ServiceConfiguration config) throws IOException { - File confFile = new File(System.getProperty(CONF_SYSTEM_PROPERTY_KEY)); - if (!confFile.exists()) { - throw new IOException("The password auth conf file does not exist"); - } else if (!confFile.isFile()) { - throw new IOException("The path is not a file"); + String data = config.getProperties().getProperty(CONF_PULSAR_PROPERTY_KEY); + if (StringUtils.isEmpty(data)) { + data = System.getProperty(CONF_SYSTEM_PROPERTY_KEY); + } + if (StringUtils.isEmpty(data)) { + throw new IOException("No basic authentication config provided"); + } + + @Cleanup BufferedReader reader = null; + try { + byte[] bytes = readData(data); + reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes))); + } catch (Exception e) { + throw new IllegalArgumentException(e); } - @Cleanup BufferedReader reader = new BufferedReader(new FileReader(confFile)); users = new HashMap<>(); for (String line : reader.lines().toArray(s -> new String[s])) { List splitLine = Arrays.asList(line.split(":")); @@ -95,7 +125,8 @@ public String authenticate(AuthenticationDataSource authData) throws Authenticat throw new AuthenticationException(msg); } } catch (AuthenticationException exception) { - AuthenticationMetrics.authenticateFailure(getClass().getSimpleName(), getAuthMethodName(), exception.getMessage()); + AuthenticationMetrics.authenticateFailure(getClass().getSimpleName(), getAuthMethodName(), + exception.getMessage()); throw exception; } AuthenticationMetrics.authenticateSuccess(getClass().getSimpleName(), getAuthMethodName()); diff --git a/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasicTest.java b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasicTest.java new file mode 100644 index 00000000000000..ef7dca23e550f1 --- /dev/null +++ b/pulsar-broker-common/src/test/java/org/apache/pulsar/broker/authentication/AuthenticationProviderBasicTest.java @@ -0,0 +1,104 @@ +/** + * 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.pulsar.broker.authentication; + +import static org.testng.Assert.assertEquals; +import com.google.common.io.Resources; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.Properties; +import javax.naming.AuthenticationException; +import lombok.Cleanup; +import org.apache.pulsar.broker.ServiceConfiguration; +import org.apache.pulsar.common.api.AuthData; +import org.testng.annotations.Test; + +public class AuthenticationProviderBasicTest { + private final String basicAuthConf = Resources.getResource("authentication/basic/.htpasswd").getPath(); + private final String basicAuthConfBase64 = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(basicAuthConf))); + + public AuthenticationProviderBasicTest() throws IOException { + } + + private void testAuthenticate(AuthenticationProviderBasic provider) throws AuthenticationException { + AuthData authData = AuthData.of("superUser2:superpassword".getBytes(StandardCharsets.UTF_8)); + provider.newAuthState(authData, null, null); + } + + @Test + public void testLoadFileFromPulsarProperties() throws Exception { + @Cleanup + AuthenticationProviderBasic provider = new AuthenticationProviderBasic(); + ServiceConfiguration serviceConfiguration = new ServiceConfiguration(); + Properties properties = new Properties(); + properties.setProperty("basicAuthConf", basicAuthConf); + serviceConfiguration.setProperties(properties); + provider.initialize(serviceConfiguration); + testAuthenticate(provider); + } + + @Test + public void testLoadBase64FromPulsarProperties() throws Exception { + @Cleanup + AuthenticationProviderBasic provider = new AuthenticationProviderBasic(); + ServiceConfiguration serviceConfiguration = new ServiceConfiguration(); + Properties properties = new Properties(); + properties.setProperty("basicAuthConf", basicAuthConfBase64); + serviceConfiguration.setProperties(properties); + provider.initialize(serviceConfiguration); + testAuthenticate(provider); + } + + @Test + public void testLoadFileFromSystemProperties() throws Exception { + @Cleanup + AuthenticationProviderBasic provider = new AuthenticationProviderBasic(); + ServiceConfiguration serviceConfiguration = new ServiceConfiguration(); + System.setProperty("pulsar.auth.basic.conf", basicAuthConf); + provider.initialize(serviceConfiguration); + testAuthenticate(provider); + } + + @Test + public void testLoadBase64FromSystemProperties() throws Exception { + @Cleanup + AuthenticationProviderBasic provider = new AuthenticationProviderBasic(); + ServiceConfiguration serviceConfiguration = new ServiceConfiguration(); + System.setProperty("pulsar.auth.basic.conf", basicAuthConfBase64); + provider.initialize(serviceConfiguration); + testAuthenticate(provider); + } + + @Test + public void testReadData() throws Exception { + byte[] data = Files.readAllBytes(Paths.get(basicAuthConf)); + String base64Data = Base64.getEncoder().encodeToString(data); + + // base64 format + assertEquals(AuthenticationProviderBasic.readData("data:;base64," + base64Data), data); + assertEquals(AuthenticationProviderBasic.readData(base64Data), data); + + // file format + assertEquals(AuthenticationProviderBasic.readData("file://" + basicAuthConf), data); + assertEquals(AuthenticationProviderBasic.readData(basicAuthConf), data); + } +} diff --git a/pulsar-broker-common/src/test/resources/authentication/basic/.htpasswd b/pulsar-broker-common/src/test/resources/authentication/basic/.htpasswd new file mode 100644 index 00000000000000..b1a099a5f0ecba --- /dev/null +++ b/pulsar-broker-common/src/test/resources/authentication/basic/.htpasswd @@ -0,0 +1,2 @@ +superUser:mQQQIsyvvKRtU +superUser2:$apr1$foobarmq$kuSZlLgOITksCkRgl57ie/