This is an automated email from the ASF dual-hosted git repository.
ctubbsii pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.1 by this push:
new f54a2b214a Fix CredentialProviderToken serialization (#4580)
f54a2b214a is described below
commit f54a2b214ab5ac38452536d5c9fd992813b1ba45
Author: Christopher Tubbs <[email protected]>
AuthorDate: Wed Jul 24 09:07:08 2024 -0400
Fix CredentialProviderToken serialization (#4580)
* Fix the serialization of CredentialProviderToken, so it serializes the
name and credential provider paths, rather than the resolved password
from the provider
* Add comments to reserve specific magic numbers/versions for
serialization to separate it from PasswordToken's serialization
* Mark internal method as private
* Also fix issue in CreateToken where only the most recent property was
being used to initialize a token, instead of all the properties
* Add test of end-to-end serialization and deserialization
* Use var in a few more places
* Update test keystore with unique passwords for each jceks entry
* Document hadoop commands for modifying the jceks files
This fixes #4573
---
.../security/tokens/CredentialProviderToken.java | 26 ++++++++-
.../core/client/security/tokens/PasswordToken.java | 1 +
.../org/apache/accumulo/core/util/CreateToken.java | 2 +-
.../tokens/CredentialProviderTokenTest.java | 65 ++++++++++++++++-----
core/src/test/resources/passwords.jceks | Bin 963 -> 967 bytes
5 files changed, 78 insertions(+), 16 deletions(-)
diff --git
a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderToken.java
b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderToken.java
index 4db45c8f4e..66af59395b 100644
---
a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderToken.java
+++
b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderToken.java
@@ -20,6 +20,8 @@ package org.apache.accumulo.core.client.security.tokens;
import static java.util.Objects.requireNonNull;
+import java.io.DataInput;
+import java.io.DataOutput;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.LinkedHashSet;
@@ -46,7 +48,7 @@ public class CredentialProviderToken extends PasswordToken {
setWithCredentialProviders(name, credentialProviders);
}
- protected void setWithCredentialProviders(String name, String
credentialProviders)
+ private void setWithCredentialProviders(String name, String
credentialProviders)
throws IOException {
this.name = name;
this.credentialProviders = credentialProviders;
@@ -63,6 +65,25 @@ public class CredentialProviderToken extends PasswordToken {
setPassword(CharBuffer.wrap(password));
}
+ @Override
+ public void readFields(DataInput arg0) throws IOException {
+ // -1000 to -1999 is reserved for CredentialProviderToken; see
PasswordToken
+ int version = arg0.readInt();
+ if (version != -1000) {
+ throw new IOException("Invalid CredentialProviderToken");
+ }
+ name = arg0.readUTF();
+ credentialProviders = arg0.readUTF();
+ setWithCredentialProviders(name, credentialProviders);
+ }
+
+ @Override
+ public void write(DataOutput arg0) throws IOException {
+ arg0.writeInt(-1000);
+ arg0.writeUTF(name);
+ arg0.writeUTF(credentialProviders);
+ }
+
/**
* @return Name used to extract Accumulo user password from
CredentialProvider
*
@@ -114,7 +135,8 @@ public class CredentialProviderToken extends PasswordToken {
@Override
public CredentialProviderToken clone() {
CredentialProviderToken clone = (CredentialProviderToken) super.clone();
- clone.setPassword(this.getPassword());
+ clone.name = name;
+ clone.credentialProviders = credentialProviders;
return clone;
}
diff --git
a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/PasswordToken.java
b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/PasswordToken.java
index 8390f0f6e1..45f51cc4de 100644
---
a/core/src/main/java/org/apache/accumulo/core/client/security/tokens/PasswordToken.java
+++
b/core/src/main/java/org/apache/accumulo/core/client/security/tokens/PasswordToken.java
@@ -89,6 +89,7 @@ public class PasswordToken implements AuthenticationToken {
int version = arg0.readInt();
// -1 is null, consistent with legacy format; legacy format length must be
>= -1
// so, use -2 as a magic number to indicate the new format
+ // -1000 to -1999 is reserved for CredentialProviderToken
if (version == -1) {
password = null;
} else if (version == -2) {
diff --git a/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
b/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
index a545c0d92d..f17cf675d6 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/CreateToken.java
@@ -107,8 +107,8 @@ public class CreateToken implements KeywordExecutable {
}
}
props.put(tp.getKey(), input);
- token.init(props);
}
+ token.init(props);
System.out.println("auth.type = " + opts.tokenClassName);
System.out.println("auth.principal = " + principal);
System.out.println("auth.token = " + ClientProperty.encodeToken(token));
diff --git
a/core/src/test/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderTokenTest.java
b/core/src/test/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderTokenTest.java
index 722e0f7e64..17c1b19569 100644
---
a/core/src/test/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderTokenTest.java
+++
b/core/src/test/java/org/apache/accumulo/core/client/security/tokens/CredentialProviderTokenTest.java
@@ -22,8 +22,13 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.net.URL;
@@ -35,7 +40,11 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
public class CredentialProviderTokenTest {
- // Keystore contains: {'root.password':'password', 'bob.password':'bob'}
+ // Keystore contains (no password): {'root.password':'secret',
'bob.password':'hush'}
+ // Some useful commands to update the file are:
+ // bin/hadoop credential list -provider
localjceks://file/path/to/passwords.jceks
+ // bin/hadoop credential delete bob.password -provider
localjceks://file/path/to/passwords.jceks
+ // bin/hadoop credential create bob.password -provider
localjceks://file/path/to/passwords.jceks
private static String keystorePath;
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN",
@@ -49,20 +58,50 @@ public class CredentialProviderTokenTest {
@Test
public void testPasswordsFromCredentialProvider() throws Exception {
- CredentialProviderToken token = new
CredentialProviderToken("root.password", keystorePath);
+ var token = new CredentialProviderToken("root.password", keystorePath);
assertEquals("root.password", token.getName());
assertEquals(keystorePath, token.getCredentialProviders());
- assertArrayEquals("password".getBytes(UTF_8), token.getPassword());
+ assertArrayEquals("secret".getBytes(UTF_8), token.getPassword());
token = new CredentialProviderToken("bob.password", keystorePath);
- assertArrayEquals("bob".getBytes(UTF_8), token.getPassword());
+ assertArrayEquals("hush".getBytes(UTF_8), token.getPassword());
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ var token = new CredentialProviderToken("bob.password", keystorePath);
+ assertEquals("bob.password", token.getName());
+ assertEquals(keystorePath, token.getCredentialProviders());
+ assertArrayEquals("hush".getBytes(UTF_8), token.getPassword());
+ byte[] serialized;
+ try (var baos = new ByteArrayOutputStream(); var out = new
DataOutputStream(baos)) {
+ token.write(out);
+ serialized = baos.toByteArray();
+ }
+ // verify the serialized form only contains exactly what is expected and
nothing else
+ try (var bais = new ByteArrayInputStream(serialized); var in = new
DataInputStream(bais)) {
+ assertEquals(-1000, in.readInt());
+ assertEquals("bob.password", in.readUTF());
+ assertEquals(keystorePath, in.readUTF());
+ assertEquals(0, in.available());
+ }
+ // verify deserialization to a new token
+ var token2 = new CredentialProviderToken();
+ try (var bais = new ByteArrayInputStream(serialized); var in = new
DataInputStream(bais)) {
+ token2.readFields(in);
+ }
+ assertEquals("bob.password", token2.getName());
+ assertEquals(keystorePath, token2.getCredentialProviders());
+ assertArrayEquals("hush".getBytes(UTF_8), token2.getPassword());
+ assertNotSame(token, token2);
+ assertEquals(token, token2);
}
@Test
public void testEqualityAfterInit() throws Exception {
- CredentialProviderToken token = new
CredentialProviderToken("root.password", keystorePath);
+ var token = new CredentialProviderToken("root.password", keystorePath);
- CredentialProviderToken uninitializedToken = new CredentialProviderToken();
+ var uninitializedToken = new CredentialProviderToken();
Properties props = new Properties();
props.put(CredentialProviderToken.NAME_PROPERTY, "root.password");
props.put(CredentialProviderToken.CREDENTIAL_PROVIDERS_PROPERTY,
keystorePath);
@@ -73,8 +112,8 @@ public class CredentialProviderTokenTest {
@Test
public void cloneReturnsCorrectObject() throws Exception {
- CredentialProviderToken token = new
CredentialProviderToken("root.password", keystorePath);
- CredentialProviderToken clone = token.clone();
+ var token = new CredentialProviderToken("root.password", keystorePath);
+ var clone = token.clone();
assertEquals(token, clone);
assertArrayEquals(token.getPassword(), clone.getPassword());
@@ -82,22 +121,22 @@ public class CredentialProviderTokenTest {
@Test
public void missingProperties() {
- CredentialProviderToken token = new CredentialProviderToken();
+ var token = new CredentialProviderToken();
assertThrows(IllegalArgumentException.class, () -> token.init(new
Properties()));
}
@Test
public void missingNameProperty() {
- CredentialProviderToken token = new CredentialProviderToken();
- Properties props = new Properties();
+ var token = new CredentialProviderToken();
+ var props = new Properties();
props.put(CredentialProviderToken.NAME_PROPERTY, "root.password");
assertThrows(IllegalArgumentException.class, () -> token.init(props));
}
@Test
public void missingProviderProperty() {
- CredentialProviderToken token = new CredentialProviderToken();
- Properties props = new Properties();
+ var token = new CredentialProviderToken();
+ var props = new Properties();
props.put(CredentialProviderToken.CREDENTIAL_PROVIDERS_PROPERTY,
keystorePath);
assertThrows(IllegalArgumentException.class, () -> token.init(props));
}
diff --git a/core/src/test/resources/passwords.jceks
b/core/src/test/resources/passwords.jceks
index 9bbb6bc203..d872ee59a7 100755
Binary files a/core/src/test/resources/passwords.jceks and
b/core/src/test/resources/passwords.jceks differ