-
Notifications
You must be signed in to change notification settings - Fork 618
[server] Only super.users can modify configuration of super.user info. #4229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,19 +21,25 @@ | |||||||||||||
| import org.apache.fluss.config.ConfigOptions; | ||||||||||||||
| import org.apache.fluss.config.Configuration; | ||||||||||||||
| import org.apache.fluss.config.cluster.ServerReconfigurable; | ||||||||||||||
| import org.apache.fluss.exception.AuthorizationException; | ||||||||||||||
| import org.apache.fluss.exception.ConfigException; | ||||||||||||||
| import org.apache.fluss.rpc.RpcGatewayService; | ||||||||||||||
| import org.apache.fluss.rpc.protocol.ApiManager; | ||||||||||||||
| import org.apache.fluss.rpc.protocol.NetworkProtocolPlugin; | ||||||||||||||
| import org.apache.fluss.security.acl.FlussPrincipal; | ||||||||||||||
| import org.apache.fluss.security.auth.AuthenticationFactory; | ||||||||||||||
| import org.apache.fluss.security.auth.PlainTextAuthenticationPlugin; | ||||||||||||||
| import org.apache.fluss.shaded.netty4.io.netty.channel.ChannelHandler; | ||||||||||||||
|
|
||||||||||||||
| import javax.annotation.Nullable; | ||||||||||||||
|
|
||||||||||||||
| import java.util.Collections; | ||||||||||||||
| import java.util.LinkedHashMap; | ||||||||||||||
| import java.util.List; | ||||||||||||||
| import java.util.Map; | ||||||||||||||
| import java.util.Objects; | ||||||||||||||
| import java.util.Optional; | ||||||||||||||
| import java.util.Set; | ||||||||||||||
| import java.util.regex.Matcher; | ||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -42,6 +48,7 @@ public class FlussProtocolPlugin implements NetworkProtocolPlugin, ServerReconfi | |||||||||||||
|
|
||||||||||||||
| private static final String PLAIN_CREDENTIALS_CONFIG = | ||||||||||||||
| ConfigOptions.SERVER_SASL_CREDENTIALS.key(); | ||||||||||||||
| private static final String USER_PRINCIPAL_TYPE = "User"; | ||||||||||||||
|
|
||||||||||||||
| /** Pattern to match {@code user_<username>="<password>"} entries in JAAS config strings. */ | ||||||||||||||
| private static final Pattern JAAS_USER_PATTERN = Pattern.compile("user_(\\w+)=\"([^\"]*)\""); | ||||||||||||||
|
|
@@ -65,6 +72,9 @@ public class FlussProtocolPlugin implements NetworkProtocolPlugin, ServerReconfi | |||||||||||||
| private final List<String> listeners; | ||||||||||||||
| private final RequestsMetrics requestsMetrics; | ||||||||||||||
| private Configuration conf; | ||||||||||||||
| private Set<FlussPrincipal> superUsers; | ||||||||||||||
| private boolean principalIgnoreCase; | ||||||||||||||
|
|
||||||||||||||
| /** Initial credentials from `security.sasl.plain.jaas.config`. */ | ||||||||||||||
| private Map<String, String> initialPlainCredentialsFromJaasConfig; | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -86,6 +96,8 @@ public String name() { | |||||||||||||
| @Override | ||||||||||||||
| public void setup(Configuration conf) { | ||||||||||||||
| this.conf = new Configuration(conf); | ||||||||||||||
| this.principalIgnoreCase = this.conf.get(ConfigOptions.SECURITY_ACL_PRINCIPAL_IGNORE_CASE); | ||||||||||||||
| this.superUsers = parseSuperUsers(this.conf); | ||||||||||||||
| this.initialPlainCredentialsFromJaasConfig = parseCredentialsFromJaasConfig(conf); | ||||||||||||||
| enrichWithJaasConfig(conf); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -138,6 +150,13 @@ public void validate(Configuration newConfig) throws ConfigException { | |||||||||||||
| generateMergedJaasConfig(newCredentials); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| @Override | ||||||||||||||
| public void validate(Configuration newConfig, @Nullable FlussPrincipal requester) | ||||||||||||||
| throws ConfigException { | ||||||||||||||
| authorizeSuperUserCredentialChanges(readPlainCredentials(newConfig), requester); | ||||||||||||||
| validate(newConfig); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| @Override | ||||||||||||||
| public void reconfigure(Configuration newConfig) throws ConfigException { | ||||||||||||||
| enrichWithJaasConfig(newConfig); | ||||||||||||||
|
|
@@ -209,11 +228,7 @@ private static void validatePassword(int index, String username, String password | |||||||||||||
| * @return the generated JAAS config string | ||||||||||||||
| */ | ||||||||||||||
| private String generateMergedJaasConfig(Map<String, String> newCredentials) { | ||||||||||||||
| Map<String, String> mergedCredentials = | ||||||||||||||
| new LinkedHashMap<>(initialPlainCredentialsFromJaasConfig); | ||||||||||||||
| if (newCredentials != null) { | ||||||||||||||
| mergedCredentials.putAll(newCredentials); | ||||||||||||||
| } | ||||||||||||||
| Map<String, String> mergedCredentials = mergePlainCredentials(newCredentials); | ||||||||||||||
|
|
||||||||||||||
| StringBuilder sb = | ||||||||||||||
| new StringBuilder( | ||||||||||||||
|
|
@@ -225,6 +240,58 @@ private String generateMergedJaasConfig(Map<String, String> newCredentials) { | |||||||||||||
| return sb.toString(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private Map<String, String> mergePlainCredentials(Map<String, String> plainCredentials) { | ||||||||||||||
| Map<String, String> mergedCredentials = | ||||||||||||||
| new LinkedHashMap<>(initialPlainCredentialsFromJaasConfig); | ||||||||||||||
| if (plainCredentials != null) { | ||||||||||||||
| mergedCredentials.putAll(plainCredentials); | ||||||||||||||
| } | ||||||||||||||
| return mergedCredentials; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Rejects the change if the requester is not a configured super user but the credentials of a | ||||||||||||||
| * configured super user would be added, removed or modified. | ||||||||||||||
| */ | ||||||||||||||
| private void authorizeSuperUserCredentialChanges( | ||||||||||||||
| @Nullable Map<String, String> newCredentials, @Nullable FlussPrincipal requester) { | ||||||||||||||
| if (requester == null || isSuperUser(requester)) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (!Objects.equals( | ||||||||||||||
| superUserCredentials(currentPlainCredentials), | ||||||||||||||
| superUserCredentials(newCredentials))) { | ||||||||||||||
| throw new AuthorizationException( | ||||||||||||||
| "Only configured super users may alter credentials of configured super users."); | ||||||||||||||
|
Comment on lines
+265
to
+266
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Improve the exception to make it more clear. |
||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /** Returns the merged credentials that belong to a configured super user. */ | ||||||||||||||
| private Map<String, String> superUserCredentials(@Nullable Map<String, String> credentials) { | ||||||||||||||
| Map<String, String> superUserCredentials = new LinkedHashMap<>(); | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need |
||||||||||||||
| mergePlainCredentials(credentials) | ||||||||||||||
| .forEach( | ||||||||||||||
| (user, password) -> { | ||||||||||||||
| if (isSuperUser(new FlussPrincipal(user, USER_PRINCIPAL_TYPE))) { | ||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the |
||||||||||||||
| superUserCredentials.put(user, password); | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
| return superUserCredentials; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private boolean isSuperUser(FlussPrincipal principal) { | ||||||||||||||
| return superUsers.stream() | ||||||||||||||
| .anyMatch(superUser -> superUser.matches(principal, principalIgnoreCase)); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static Set<FlussPrincipal> parseSuperUsers(Configuration configuration) { | ||||||||||||||
| return configuration | ||||||||||||||
| .getOptional(ConfigOptions.SUPER_USERS) | ||||||||||||||
| .map(FlussPrincipal::parsePrincipals) | ||||||||||||||
| .orElse(Collections.emptySet()); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private static Map<String, String> parseCredentialsFromJaasConfig(Configuration configuration) { | ||||||||||||||
| Map<String, String> credentials = new LinkedHashMap<>(); | ||||||||||||||
| String existingJaas = configuration.getString(ConfigOptions.SERVER_SASL_PLAIN_JAAS_CONFIG); | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use
superinstead ofguestto represent the super user? The current test code is confusing, particularly when encountering the restriction on modifying the guest password. Additionally, please add comments near the test code to clarify which users are designated as super users and to explain that the CLUSTER ALL permission does not allow modifications to super user accounts.