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 <ctubb...@apache.org>
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

Reply via email to