Skip to content

Commit 8135f4b

Browse files
committed
NullPointerException When Authentication Is Enabled but sidecar_internal Schema Is Disabled
1 parent 2e7fb30 commit 8135f4b

9 files changed

Lines changed: 200 additions & 0 deletions

File tree

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
0.2.0
22
-----
3+
* NullPointerException When Authentication Is Enabled but sidecar_internal Schema Is Disabled (CASSSIDECAR-331)
34
* Do not log whole exception when schema is not found (CASSSIDECAR-250)
45
* Add comprehensive OpenAPI documentation (CASSSIDECAR-176)
56
* Fix type used for reading member_of column in SystemAuthDatabaseAccessor (CASSSIDECAR-333)

server/src/main/java/org/apache/cassandra/sidecar/acl/AuthCache.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ public Map<K, V> getAll()
119119
return Collections.unmodifiableMap(cache.asMap());
120120
}
121121

122+
/**
123+
* Invalidate a key.
124+
* @param k key to invalidate
125+
*/
126+
public void invalidate(K k)
127+
{
128+
if (cache != null)
129+
{
130+
cache.invalidate(k);
131+
}
132+
}
133+
122134
private LoadingCache<K, V> initCache()
123135
{
124136
return Caffeine.newBuilder()

server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/AuthenticationHandlerFactory.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.vertx.core.Vertx;
2424
import io.vertx.ext.web.handler.impl.AuthenticationHandlerInternal;
2525
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
26+
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
2627
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
2728

2829
/**
@@ -45,4 +46,14 @@ public interface AuthenticationHandlerFactory
4546
AuthenticationHandlerInternal create(Vertx vertx,
4647
AccessControlConfiguration accessControlConfiguration,
4748
Map<String, String> parameters) throws ConfigurationException;
49+
50+
/**
51+
* Validates that this authentication handler factory can be used with the given configuration.
52+
*
53+
* @param sidecarConfiguration the sidecar configuration to validate against
54+
* @throws ConfigurationException if the authentication handler factory cannot be used with the given configuration
55+
*/
56+
default void validatePrerequisites(SidecarConfiguration sidecarConfiguration) throws ConfigurationException
57+
{
58+
}
4859
}

server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/JwtAuthenticationHandlerFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.vertx.core.Vertx;
2626
import io.vertx.ext.web.handler.impl.AuthenticationHandlerInternal;
2727
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
28+
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
2829
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
2930
import org.apache.cassandra.sidecar.tasks.PeriodicTaskExecutor;
3031

@@ -65,4 +66,16 @@ protected JwtParameters parameterParser(Map<String, String> parameters)
6566
{
6667
return new JwtParameterExtractor(parameters);
6768
}
69+
70+
@Override
71+
public void validatePrerequisites(SidecarConfiguration sidecarConfiguration) throws ConfigurationException
72+
{
73+
boolean isSidecarSchemaEnabled = sidecarConfiguration.serviceConfiguration()
74+
.schemaKeyspaceConfiguration()
75+
.isEnabled();
76+
if (!isSidecarSchemaEnabled)
77+
{
78+
throw new ConfigurationException("JWT auth requires sidecar schema to be enabled for role processing");
79+
}
80+
}
6881
}

server/src/main/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.cassandra.sidecar.acl.AdminIdentityResolver;
3333
import org.apache.cassandra.sidecar.acl.IdentityToRoleCache;
3434
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
35+
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
3536
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
3637

3738
/**
@@ -106,4 +107,16 @@ private MutualTlsAuthenticationHandler createInternal(Vertx vertx,
106107
MutualTlsAuthentication mTLSAuthProvider = new MutualTlsAuthenticationImpl(vertx, certificateValidator, certificateIdentityExtractor);
107108
return new MutualTlsAuthenticationHandler(mTLSAuthProvider, identityToRoleCache);
108109
}
110+
111+
@Override
112+
public void validatePrerequisites(SidecarConfiguration sidecarConfiguration) throws ConfigurationException
113+
{
114+
boolean isSidecarSchemaEnabled = sidecarConfiguration.serviceConfiguration()
115+
.schemaKeyspaceConfiguration()
116+
.isEnabled();
117+
if (!isSidecarSchemaEnabled)
118+
{
119+
throw new ConfigurationException("mTLS auth requires sidecar schema to be enabled for role processing");
120+
}
121+
}
109122
}

server/src/main/java/org/apache/cassandra/sidecar/modules/AuthModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ VertxRoute chainAuthHandler(Vertx vertx,
141141
throw new RuntimeException(String.format("Implementation for class %s has not been registered",
142142
config.className()));
143143
}
144+
factory.validatePrerequisites(sidecarConfiguration);
144145
chainAuthHandler.add(factory.create(vertx, accessControlConfiguration, config.namedParameters()));
145146
}
146147

@@ -180,6 +181,10 @@ AuthorizationProvider authorizationProvider(SidecarConfiguration sidecarConfigur
180181
}
181182
if (config.className().equalsIgnoreCase(RoleBasedAuthorizationProvider.class.getName()))
182183
{
184+
if (!sidecarConfiguration.serviceConfiguration().schemaKeyspaceConfiguration().isEnabled())
185+
{
186+
throw new ConfigurationException(config.className() + " requires sidecar schema to be enabled for role permissions storage");
187+
}
183188
return new RoleBasedAuthorizationProvider(roleAuthorizationsCache);
184189
}
185190
throw new ConfigurationException("Unrecognized authorization provider " + config.className() + " set");

server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/JWTAuthenticationHandlerFactoryTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@
2424

2525
import io.vertx.core.Vertx;
2626
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
27+
import org.apache.cassandra.sidecar.config.SchemaKeyspaceConfiguration;
28+
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
29+
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
30+
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
2731
import org.apache.cassandra.sidecar.tasks.PeriodicTaskExecutor;
2832

2933
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3034
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.when;
3136

3237
/**
3338
* Test for {@link JwtAuthenticationHandlerFactory}
@@ -55,4 +60,24 @@ void testInvalidParameters()
5560
.isInstanceOf(IllegalArgumentException.class)
5661
.hasMessage("Missing client_id JWT parameter");
5762
}
63+
64+
@Test
65+
void testValidatePrerequisitesWithSchemaDisabled()
66+
{
67+
PeriodicTaskExecutor mockTaskExecutor = mock(PeriodicTaskExecutor.class);
68+
JwtAuthenticationHandlerFactory factory = new JwtAuthenticationHandlerFactory(mockRoleProcessor, mockTaskExecutor);
69+
70+
SidecarConfiguration mockSidecarConfig = mock(SidecarConfiguration.class);
71+
ServiceConfiguration mockServiceConfig = mock(ServiceConfiguration.class);
72+
SchemaKeyspaceConfiguration mockSchemaConfig = mock(SchemaKeyspaceConfiguration.class);
73+
74+
when(mockSidecarConfig.serviceConfiguration()).thenReturn(mockServiceConfig);
75+
when(mockServiceConfig.schemaKeyspaceConfiguration()).thenReturn(mockSchemaConfig);
76+
when(mockSchemaConfig.isEnabled()).thenReturn(false);
77+
78+
// Should throw exception when schema is disabled
79+
assertThatThrownBy(() -> factory.validatePrerequisites(mockSidecarConfig))
80+
.isInstanceOf(ConfigurationException.class)
81+
.hasMessage("JWT auth requires sidecar schema to be enabled for role processing");
82+
}
5883
}

server/src/test/java/org/apache/cassandra/sidecar/acl/authentication/MutualTlsAuthenticationHandlerFactoryTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
3535
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
3636
import org.apache.cassandra.sidecar.config.CacheConfiguration;
37+
import org.apache.cassandra.sidecar.config.SchemaKeyspaceConfiguration;
38+
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
3739
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
3840
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
3941
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.sidecar.modules;
20+
21+
import java.util.List;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import io.vertx.core.Vertx;
26+
import org.apache.cassandra.sidecar.acl.authentication.AuthenticationHandlerFactory;
27+
import org.apache.cassandra.sidecar.acl.authentication.AuthenticationHandlerFactoryRegistry;
28+
import org.apache.cassandra.sidecar.acl.authentication.MutualTlsAuthenticationHandlerFactory;
29+
import org.apache.cassandra.sidecar.acl.authorization.RoleBasedAuthorizationProvider;
30+
import org.apache.cassandra.sidecar.config.AccessControlConfiguration;
31+
import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
32+
import org.apache.cassandra.sidecar.config.SchemaKeyspaceConfiguration;
33+
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
34+
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
35+
import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
36+
37+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
38+
import static org.mockito.ArgumentMatchers.any;
39+
import static org.mockito.Mockito.mock;
40+
import static org.mockito.Mockito.when;
41+
42+
/**
43+
* Test for {@link AuthModule}
44+
*/
45+
class AuthModuleTest
46+
{
47+
@Test
48+
void testAuthorizationProviderWithSchemaEnabled()
49+
{
50+
AuthModule authModule = new AuthModule();
51+
SidecarConfiguration mockSidecarConfig = createSidecarConfiguration(true, true, true);
52+
53+
// Should not throw when schema is enabled
54+
authModule.authorizationProvider(mockSidecarConfig, null);
55+
}
56+
57+
@Test
58+
void testRoleBasedAuthorizationProviderWithSchemaDisabled()
59+
{
60+
AuthModule authModule = new AuthModule();
61+
SidecarConfiguration mockSidecarConfig = createSidecarConfiguration(true, true,false);
62+
63+
// Should throw when RoleBasedAuthorizationProvider is used but schema is disabled
64+
assertThatThrownBy(() -> authModule.authorizationProvider(mockSidecarConfig, null))
65+
.isInstanceOf(ConfigurationException.class)
66+
.hasMessage(RoleBasedAuthorizationProvider.class.getName() +
67+
" requires sidecar schema to be enabled for role permissions storage");
68+
}
69+
70+
@Test
71+
void testChainAuthHandlerValidatesPrerequisites()
72+
{
73+
AuthModule authModule = new AuthModule();
74+
Vertx mockVertx = mock(Vertx.class);
75+
76+
SidecarConfiguration mockSidecarConfig = createSidecarConfiguration(true, false, false);
77+
78+
AuthenticationHandlerFactoryRegistry mockRegistry = mock(AuthenticationHandlerFactoryRegistry.class);
79+
AuthenticationHandlerFactory mtlsFactory = new MutualTlsAuthenticationHandlerFactory(null, null);
80+
when(mockRegistry.getFactory(any())).thenReturn(mtlsFactory);
81+
82+
// Should throw ConfigurationException when factory validates prerequisites
83+
assertThatThrownBy(() -> authModule.chainAuthHandler(mockVertx, mockSidecarConfig, mockRegistry))
84+
.isInstanceOf(ConfigurationException.class)
85+
.hasMessage("mTLS auth requires sidecar schema to be enabled for role processing");
86+
}
87+
88+
private SidecarConfiguration createSidecarConfiguration(boolean authenticationEnabled,
89+
boolean authorizationEnabled,
90+
boolean schemaEnabled)
91+
{
92+
SidecarConfiguration mockSidecarConfig = mock(SidecarConfiguration.class);
93+
ServiceConfiguration mockServiceConfig = mock(ServiceConfiguration.class);
94+
SchemaKeyspaceConfiguration mockSchemaConfig = mock(SchemaKeyspaceConfiguration.class);
95+
AccessControlConfiguration mockAccessControlConfig = mock(AccessControlConfiguration.class);
96+
ParameterizedClassConfiguration mockAuthenticatorConfig = mock(ParameterizedClassConfiguration.class);
97+
ParameterizedClassConfiguration mockAuthorizerConfig = mock(ParameterizedClassConfiguration.class);
98+
99+
when(mockSidecarConfig.serviceConfiguration()).thenReturn(mockServiceConfig);
100+
when(mockServiceConfig.schemaKeyspaceConfiguration()).thenReturn(mockSchemaConfig);
101+
when(mockSchemaConfig.isEnabled()).thenReturn(schemaEnabled);
102+
when(mockSidecarConfig.accessControlConfiguration()).thenReturn(mockAccessControlConfig);
103+
when(mockAccessControlConfig.enabled()).thenReturn(true);
104+
105+
if (authenticationEnabled)
106+
{
107+
when(mockAccessControlConfig.authenticatorsConfiguration()).thenReturn(List.of(mockAuthenticatorConfig));
108+
when(mockAuthenticatorConfig.className()).thenReturn(MutualTlsAuthenticationHandlerFactory.class.getName());
109+
}
110+
111+
if (authorizationEnabled)
112+
{
113+
when(mockAccessControlConfig.authorizerConfiguration()).thenReturn(mockAuthorizerConfig);
114+
when(mockAuthorizerConfig.className()).thenReturn(RoleBasedAuthorizationProvider.class.getName());
115+
}
116+
return mockSidecarConfig;
117+
}
118+
}

0 commit comments

Comments
 (0)