Skip to content

Commit 9e399e7

Browse files
authored
Issue 145: Use new Auth strings syntax (#150)
Change auth resource strings to use domain and resource identifiers. Signed-off-by: Shivesh Ranjan <[email protected]>
1 parent 0639705 commit 9e399e7

File tree

9 files changed

+95
-271
lines changed

9 files changed

+95
-271
lines changed

charts/schema-registry/templates/configmap.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ data:
3232
"DISABLE_BASIC_AUTH": "{{ .Values.authentication.disableBasicAuthentication }}"
3333
{{- end }}
3434
{{- if .Values.authentication.authorizationResourceQualifier }}
35-
"AUTHORIZATION_RESOURCE_QUALIFIER": "{{ .Values.authentication.authorizationResourceQualifier }}"
35+
"AUTHORIZATION_DOMAIN_RESOURCE_IDENTIFIER": "{{ .Values.authentication.authResourceDomain }}"
36+
"AUTHORIZATION_NAMESPACE_RESOURCE_IDENTIFIER": "{{ .Values.authentication.authResourceNamespace }}"
37+
"AUTHORIZATION_GROUP_RESOURCE_IDENTIFIER": "{{ .Values.authentication.authorizationResourceGroup }}"
3638
{{- end }}
3739
{{- if .Values.tls.enabled }}
3840
"TLS_CERT_FILE": "/etc/secret-volume/{{ .Values.tls.certFile }}"

charts/schema-registry/values.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ authentication:
6767
## authentication is enabled
6868
passwordAuthSecret:
6969
userPasswordFile:
70-
authorizationResourceQualifier: ""
7170
disableBasicAuthentication: false
71+
authResourceDomain: "prn"
72+
authNamespaceResourceIdentifier: "namespace"
73+
authGroupResourceIdentifier: "group"
7274

7375
tls:
7476
enabled: false

server/src/conf/schema-registry.config.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ schemaRegistry.security.tls.server.privateKey.pwd.location=${TLS_KEY_PASSWORD_FI
3434
## Authorization configuration
3535
schemaRegistry.security.auth.enable=${AUTHORIZATION_ENABLED}
3636
schemaRegistry.security.pwdAuthHandler.accountsDb.location=${USER_PASSWORD_FILE}
37-
schemaRegistry.security.auth.resource.qualifier=${AUTHORIZATION_RESOURCE_QUALIFIER}
37+
schemaRegistry.security.auth.resource.identifier.domain=${AUTHORIZATION_DOMAIN_RESOURCE_IDENTIFIER}
38+
schemaRegistry.security.auth.resource.identifier.namespace=${AUTHORIZATION_NAMESPACE_RESOURCE_IDENTIFIER}
39+
schemaRegistry.security.auth.resource.identifier.group=${AUTHORIZATION_GROUP_RESOURCE_IDENTIFIER}
3840
schemaRegistry.security.auth.method.basic.disable=${DISABLE_BASIC_AUTH}

server/src/main/java/io/pravega/schemaregistry/server/rest/auth/AuthHandlerManager.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
@Slf4j
3030
public class AuthHandlerManager {
3131
private final ServiceConfig serverConfig;
32-
3332
private final ConcurrentHashMap<String, AuthHandler> handlerMap;
3433

3534
public AuthHandlerManager(ServiceConfig serverConfig) {
@@ -43,7 +42,7 @@ private void loadHandlers() {
4342
if (serverConfig.isAuthEnabled()) {
4443
ServiceLoader<AuthHandler> loader = ServiceLoader.load(AuthHandler.class);
4544
for (AuthHandler handler : loader) {
46-
if (handler instanceof PasswordAuthHandler) {
45+
if (handler instanceof PasswordAuthHandler && !(handler instanceof BasicAuthHandler)) {
4746
continue;
4847
}
4948
try {

server/src/main/java/io/pravega/schemaregistry/server/rest/auth/BasicAuthHandler.java

Lines changed: 8 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -9,210 +9,23 @@
99
*/
1010
package io.pravega.schemaregistry.server.rest.auth;
1111

12-
import com.google.common.base.Charsets;
13-
import com.google.common.base.Preconditions;
14-
import com.google.common.base.Strings;
15-
import io.pravega.auth.AuthConstants;
16-
import io.pravega.auth.AuthException;
1712
import io.pravega.auth.AuthHandler;
18-
import io.pravega.auth.AuthenticationException;
1913
import io.pravega.auth.ServerConfig;
20-
import io.pravega.controller.server.security.auth.StrongPasswordProcessor;
21-
import io.pravega.controller.server.security.auth.UserPrincipal;
14+
import io.pravega.controller.server.rpc.grpc.impl.GRPCServerConfigImpl;
15+
import io.pravega.controller.server.security.auth.handler.impl.PasswordAuthHandler;
2216
import io.pravega.schemaregistry.server.rest.ServiceConfig;
23-
import lombok.Data;
17+
import io.pravega.schemaregistry.service.Config;
2418
import lombok.extern.slf4j.Slf4j;
2519

26-
import java.io.BufferedReader;
27-
import java.io.FileReader;
28-
import java.io.IOException;
29-
import java.security.NoSuchAlgorithmException;
30-
import java.security.Principal;
31-
import java.security.spec.InvalidKeySpecException;
32-
import java.util.Arrays;
33-
import java.util.Base64;
34-
import java.util.List;
35-
import java.util.concurrent.CompletionException;
36-
import java.util.concurrent.ConcurrentHashMap;
37-
import java.util.stream.Collectors;
38-
3920
@Slf4j
40-
public class BasicAuthHandler implements AuthHandler {
41-
42-
private final ConcurrentHashMap<String, PravegaACls> userMap;
43-
private final StrongPasswordProcessor encryptor;
44-
45-
public BasicAuthHandler() {
46-
userMap = new ConcurrentHashMap<>();
47-
encryptor = StrongPasswordProcessor.builder().build();
48-
}
49-
50-
private void loadPasswordFile(String userPasswordFile) {
51-
log.debug("Loading {}", userPasswordFile);
52-
53-
try (FileReader reader = new FileReader(userPasswordFile);
54-
BufferedReader lineReader = new BufferedReader(reader)) {
55-
String line;
56-
while (!Strings.isNullOrEmpty(line = lineReader.readLine())) {
57-
if (line.startsWith("#")) {
58-
continue;
59-
}
60-
String[] userFields = line.split(":");
61-
if (userFields.length >= 2) {
62-
String acls;
63-
if (userFields.length == 2) {
64-
acls = "";
65-
} else {
66-
acls = userFields[2];
67-
}
68-
userMap.put(userFields[0], new PravegaACls(userFields[1], getAcls(acls)));
69-
}
70-
}
71-
} catch (IOException e) {
72-
throw new CompletionException(e);
73-
}
74-
}
75-
76-
@Override
77-
public String getHandlerName() {
78-
return AuthConstants.BASIC;
79-
}
80-
81-
@Override
82-
public Principal authenticate(String token) throws AuthException {
83-
String[] parts = parseToken(token);
84-
String userName = parts[0];
85-
char[] password = parts[1].toCharArray();
86-
87-
try {
88-
if (userMap.containsKey(userName) && encryptor.checkPassword(password, userMap.get(userName).encryptedPassword)) {
89-
return new UserPrincipal(userName);
90-
}
91-
throw new AuthenticationException("User authentication exception");
92-
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
93-
log.warn("Exception during password authentication", e);
94-
throw new AuthenticationException(e);
95-
} finally {
96-
Arrays.fill(password, '0'); // Zero out the password for security.
97-
}
98-
}
99-
100-
@Override
101-
public Permissions authorize(String resource, Principal principal) {
102-
String userName = principal.getName();
103-
104-
if (Strings.isNullOrEmpty(userName) || !userMap.containsKey(userName)) {
105-
throw new CompletionException(new AuthenticationException(userName));
106-
}
107-
return authorizeForUser(userMap.get(userName), resource);
108-
}
109-
21+
public class BasicAuthHandler extends PasswordAuthHandler implements AuthHandler {
11022
@Override
11123
public void initialize(ServerConfig serverConfig) {
112-
loadPasswordFile(((ServiceConfig) serverConfig).getUserPasswordFilePath());
24+
super.initialize(GRPCServerConfigImpl
25+
.builder().port(Config.SERVICE_PORT)
26+
.userPasswordFile(((ServiceConfig) serverConfig).getUserPasswordFilePath()).build());
11327
}
28+
}
11429

115-
private static String[] parseToken(String token) {
116-
String[] parts = new String(Base64.getDecoder().decode(token), Charsets.UTF_8).split(":", 2);
117-
Preconditions.checkArgument(parts.length == 2, "Invalid authorization token");
118-
return parts;
119-
}
120-
121-
private Permissions authorizeForUser(PravegaACls pravegaACls, String resource) {
122-
Permissions result = Permissions.NONE;
123-
124-
/*
125-
* `*` Means a wildcard.
126-
* If It is a direct match, return the ACLs.
127-
* If it is a partial match, the target has to end with a `/`
128-
*/
129-
for (PravegaAcl acl : pravegaACls.acls) {
130-
// Separating into different blocks, to make the code more understandable.
131-
// It makes the code look a bit strange, but it is still simpler and easier to decipher than what it be
132-
// if we combine the conditions.
133-
134-
if (acl.isResource(resource)) {
135-
// Example: resource = "mygroup", acl-resource = "mygroup"
136-
137-
result = acl.permissions;
138-
break;
139-
}
140-
141-
if (acl.isResource("/*") && !resource.contains("/")) {
142-
// Example: resource = "mygroup", acl-resource ="/*"
143-
result = acl.permissions;
144-
break;
145-
}
146-
147-
if (acl.resourceEndsWith("/") && acl.resourceStartsWith(resource)) {
148-
result = acl.permissions;
149-
break;
150-
}
151-
152-
// Say, resource is mygroup/schemas. ACL specifies permission for mygroup/*.
153-
// Auth should return the the ACL's permissions in that case.
154-
if (resource.contains("/") && !resource.endsWith("/")) {
155-
String[] values = resource.split("/");
156-
String res = null;
157-
for (String value : values) {
158-
res = res == null ? value : res + "/" + value;
159-
if (acl.isResource(res + "/*")) {
160-
result = acl.permissions;
161-
break;
162-
}
163-
}
164-
}
165-
166-
if (acl.isResource("*") && acl.hasHigherPermissionsThan(result)) {
167-
result = acl.permissions;
168-
break;
169-
}
170-
}
171-
return result;
172-
}
173-
174-
private List<PravegaAcl> getAcls(String aclString) {
175-
return Arrays.stream(aclString.split(";")).map(acl -> {
176-
String[] splits = acl.split(",");
177-
if (splits.length == 0) {
178-
return null;
179-
}
180-
String resource = splits[0];
181-
String aclVal = "READ";
182-
if (splits.length >= 2) {
183-
aclVal = splits[1];
184-
185-
}
186-
return new PravegaAcl(resource,
187-
Permissions.valueOf(aclVal));
188-
}).collect(Collectors.toList());
189-
}
190-
191-
@Data
192-
private static class PravegaACls {
193-
private final String encryptedPassword;
194-
private final List<PravegaAcl> acls;
195-
}
196-
197-
@Data
198-
private static class PravegaAcl {
199-
private final String resourceRepresentation;
200-
private final Permissions permissions;
201-
202-
boolean isResource(String resource) {
203-
return resourceRepresentation.equals(resource);
204-
}
205-
206-
boolean resourceEndsWith(String resource) {
207-
return resourceRepresentation.endsWith(resource);
208-
}
20930

210-
boolean resourceStartsWith(String resource) {
211-
return resourceRepresentation.startsWith(resource);
212-
}
21331

214-
boolean hasHigherPermissionsThan(Permissions input) {
215-
return this.permissions.ordinal() > input.ordinal();
216-
}
217-
}
218-
}

server/src/main/java/io/pravega/schemaregistry/server/rest/resources/AuthResources.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
*/
1010
package io.pravega.schemaregistry.server.rest.resources;
1111

12-
import com.google.common.base.Strings;
1312
import io.pravega.schemaregistry.service.Config;
1413

1514
class AuthResources {
16-
static final String DOMAIN = Strings.isNullOrEmpty(Config.AUTH_RESOURCE_QUALIFIER) ? "" : "_" + Config.AUTH_RESOURCE_QUALIFIER + "/";
15+
static final String DOMAIN = Config.DOMAIN_RESOURCE_QUALIFIER + "::/";
1716
static final String DEFAULT_NAMESPACE = "";
18-
static final String NAMESPACE_FORMAT = DOMAIN + "%s";
19-
static final String NAMESPACE_GROUP_FORMAT = NAMESPACE_FORMAT + "/%s";
20-
static final String NAMESPACE_GROUP_SCHEMA_FORMAT = NAMESPACE_GROUP_FORMAT + "/schemas";
21-
static final String NAMESPACE_GROUP_CODEC_FORMAT = NAMESPACE_GROUP_FORMAT + "/codecs";
17+
static final String NAMESPACE_FORMAT = DOMAIN + Config.NAMESPACE_RESOURCE_QUALIFIER + ":%s";
18+
static final String NAMESPACE_GROUP_FORMAT = NAMESPACE_FORMAT + "/" + Config.GROUP_RESOURCE_QUALIFIER + ":%s";
19+
static final String NAMESPACE_GROUP_SCHEMA_FORMAT = NAMESPACE_GROUP_FORMAT + "/schemas:*";
20+
static final String NAMESPACE_GROUP_CODEC_FORMAT = NAMESPACE_GROUP_FORMAT + "/codecs:*";
2221
}

server/src/main/java/io/pravega/schemaregistry/service/Config.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ public final class Config {
6161
public static final String TLS_CERT_FILE;
6262

6363
public static final boolean AUTH_ENABLED;
64-
public static final String AUTH_RESOURCE_QUALIFIER;
64+
public static final String DOMAIN_RESOURCE_QUALIFIER;
65+
public static final String NAMESPACE_RESOURCE_QUALIFIER;
66+
public static final String GROUP_RESOURCE_QUALIFIER;
6567
public static final String USER_PASSWORD_FILE;
6668
public static final boolean DISABLE_BASIC_AUTHENTICATION;
6769

@@ -91,7 +93,9 @@ public final class Config {
9193

9294
private static final Property<Boolean> PROPERTY_AUTH_ENABLED = Property.named("security.auth.enable", false);
9395
private static final Property<String> PROPERTY_AUTH_PASSWORD_FILE = Property.named("security.pwdAuthHandler.accountsDb.location", "");
94-
private static final Property<String> PROPERTY_AUTH_RESOURCE_QUALIFIER = Property.named("security.auth.resource.qualifier", "");
96+
private static final Property<String> PROPERTY_DOMAIN_RESOURCE_QUALIFIER = Property.named("security.auth.resource.identifier.domain", "prn");
97+
private static final Property<String> PROPERTY_NAMESPACE_RESOURCE_QUALIFIER = Property.named("security.auth.resource.identifier.namespace", "namespace");
98+
private static final Property<String> PROPERTY_GROUP_RESOURCE_QUALIFIER = Property.named("security.auth.resource.identifier.group", "group");
9599
private static final Property<Boolean> PROPERTY_DISABLE_BASIC_AUTHENTICATION = Property.named("security.auth.method.basic.disable", false);
96100

97101
private static final String COMPONENT_CODE = "schemaRegistry";
@@ -124,7 +128,9 @@ public final class Config {
124128

125129
AUTH_ENABLED = p.getBoolean(PROPERTY_AUTH_ENABLED);
126130
DISABLE_BASIC_AUTHENTICATION = p.getBoolean(PROPERTY_DISABLE_BASIC_AUTHENTICATION);
127-
AUTH_RESOURCE_QUALIFIER = p.get(PROPERTY_AUTH_RESOURCE_QUALIFIER);
131+
DOMAIN_RESOURCE_QUALIFIER = p.get(PROPERTY_DOMAIN_RESOURCE_QUALIFIER);
132+
NAMESPACE_RESOURCE_QUALIFIER = p.get(PROPERTY_NAMESPACE_RESOURCE_QUALIFIER);
133+
GROUP_RESOURCE_QUALIFIER = p.get(PROPERTY_GROUP_RESOURCE_QUALIFIER);
128134
USER_PASSWORD_FILE = p.get(PROPERTY_AUTH_PASSWORD_FILE);
129135

130136
SERVICE_CONFIG = createServiceConfig();

server/src/test/java/io/pravega/schemaregistry/server/rest/resources/SchemaRegistryAuthTest.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public void groups() throws ExecutionException, InterruptedException {
194194
assertEquals(list.getContinuationToken(), ContinuationToken.fromString("token").toString());
195195
}
196196

197-
@Test
197+
@Test(timeout = 10000)
198198
public void groupSchemas() throws ExecutionException, InterruptedException {
199199
doAnswer(x -> CompletableFuture.completedFuture(true)).when(service).canRead(any(), any(), any());
200200
SchemaInfo schemaInfo = new SchemaInfo()
@@ -206,6 +206,7 @@ public void groupSchemas() throws ExecutionException, InterruptedException {
206206
AuthHelper.getAuthorizationHeader("Basic", Base64.getEncoder().encodeToString((SYSTEM_ADMIN + ":" + PASSWORD).getBytes(Charsets.UTF_8))))
207207
.async().post(Entity.entity(schemaInfo, MediaType.APPLICATION_JSON));
208208
Response response = future.get();
209+
assertEquals(response.getStatus(), 200);
209210
assertTrue(response.readEntity(CanRead.class).isCompatible());
210211

211212
future = target("v1/groups").path("mygroup").path("schemas/versions/canRead").request().header(HttpHeaders.AUTHORIZATION,
@@ -235,14 +236,14 @@ private File createAuthFile() {
235236

236237
try (FileWriter writer = new FileWriter(authFile.getAbsolutePath())) {
237238
String defaultPassword = passwordEncryptor.encryptPassword(PASSWORD);
238-
writer.write(credentialsAndAclAsString(SYSTEM_ADMIN, defaultPassword, "*,READ_UPDATE;"));
239-
writer.write(credentialsAndAclAsString(SYSTEM_READER, defaultPassword, "/*,READ"));
240-
writer.write(credentialsAndAclAsString(GROUP1_ADMIN, defaultPassword, "/group1,READ_UPDATE"));
241-
writer.write(credentialsAndAclAsString(GROUP1_USER, defaultPassword, "/group1,READ"));
242-
writer.write(credentialsAndAclAsString(GROUP2_USER, defaultPassword, "/group2,READ"));
243-
writer.write(credentialsAndAclAsString(GROUP_1_2_USER, defaultPassword, "/group1,READ;/group2,READ;/group11,READ;/group12,READ"));
244-
writer.write(credentialsAndAclAsString(NAMESPACE_USER, defaultPassword, "namespace/group1,READ_UPDATE"));
245-
writer.write(credentialsAndAclAsString(NAMESPACE_ADMIN, defaultPassword, "namespace/*,READ_UPDATE"));
239+
writer.write(credentialsAndAclAsString(SYSTEM_ADMIN, defaultPassword, "prn::*,READ_UPDATE;"));
240+
writer.write(credentialsAndAclAsString(SYSTEM_READER, defaultPassword, "prn::/namespace:*,READ"));
241+
writer.write(credentialsAndAclAsString(GROUP1_ADMIN, defaultPassword, "prn::/namespace:/group:group1,READ_UPDATE"));
242+
writer.write(credentialsAndAclAsString(GROUP1_USER, defaultPassword, "prn::/namespace:/group:group1,READ"));
243+
writer.write(credentialsAndAclAsString(GROUP2_USER, defaultPassword, "prn::/namespace:/group:group2,READ"));
244+
writer.write(credentialsAndAclAsString(GROUP_1_2_USER, defaultPassword, "prn::/namespace:/group:group1,READ;prn::/namespace:/group:group2,READ;prn::/namespace:/group:group11,READ;prn::/namespace:/group:group12,READ"));
245+
writer.write(credentialsAndAclAsString(NAMESPACE_USER, defaultPassword, "prn::/namespace:namespace/group:group1,READ_UPDATE"));
246+
writer.write(credentialsAndAclAsString(NAMESPACE_ADMIN, defaultPassword, "prn::/namespace:namespace/*,READ_UPDATE"));
246247
}
247248
return authFile;
248249
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {

0 commit comments

Comments
 (0)