diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenRequest.java
index 6c1b394355e..4f1302533d9 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenRequest.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/CreateTokenRequest.java
@@ -40,6 +40,7 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
private final String username;
private final char[] password;
private final String refreshToken;
+ private final char[] kerberosTicket;
/**
* General purpose constructor. This constructor is typically not useful, and one of the following factory methods should be used
@@ -48,10 +49,11 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
*
{@link #passwordGrant(String, char[])}
* {@link #refreshTokenGrant(String)}
* {@link #clientCredentialsGrant()}
+ * {@link #kerberosGrant(char[])}
*
*/
public CreateTokenRequest(String grantType, @Nullable String scope, @Nullable String username, @Nullable char[] password,
- @Nullable String refreshToken) {
+ @Nullable String refreshToken, @Nullable char[] kerberosTicket) {
if (Strings.isNullOrEmpty(grantType)) {
throw new IllegalArgumentException("grant_type is required");
}
@@ -60,6 +62,7 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
this.password = password;
this.scope = scope;
this.refreshToken = refreshToken;
+ this.kerberosTicket = kerberosTicket;
}
public static CreateTokenRequest passwordGrant(String username, char[] password) {
@@ -69,18 +72,25 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
if (password == null || password.length == 0) {
throw new IllegalArgumentException("password is required");
}
- return new CreateTokenRequest("password", null, username, password, null);
+ return new CreateTokenRequest("password", null, username, password, null, null);
}
public static CreateTokenRequest refreshTokenGrant(String refreshToken) {
if (Strings.isNullOrEmpty(refreshToken)) {
throw new IllegalArgumentException("refresh_token is required");
}
- return new CreateTokenRequest("refresh_token", null, null, null, refreshToken);
+ return new CreateTokenRequest("refresh_token", null, null, null, refreshToken, null);
}
public static CreateTokenRequest clientCredentialsGrant() {
- return new CreateTokenRequest("client_credentials", null, null, null, null);
+ return new CreateTokenRequest("client_credentials", null, null, null, null, null);
+ }
+
+ public static CreateTokenRequest kerberosGrant(char[] kerberosTicket) {
+ if (kerberosTicket == null || kerberosTicket.length == 0) {
+ throw new IllegalArgumentException("kerberos ticket is required");
+ }
+ return new CreateTokenRequest("_kerberos", null, null, null, null, kerberosTicket);
}
public String getGrantType() {
@@ -103,6 +113,10 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
return refreshToken;
}
+ public char[] getKerberosTicket() {
+ return kerberosTicket;
+ }
+
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
@@ -124,6 +138,14 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
if (refreshToken != null) {
builder.field("refresh_token", refreshToken);
}
+ if (kerberosTicket != null) {
+ byte[] kerberosTicketBytes = CharArrays.toUtf8Bytes(kerberosTicket);
+ try {
+ builder.field("kerberos_ticket").utf8Value(kerberosTicketBytes, 0, kerberosTicketBytes.length);
+ } finally {
+ Arrays.fill(kerberosTicketBytes, (byte) 0);
+ }
+ }
return builder.endObject();
}
@@ -140,13 +162,15 @@ public final class CreateTokenRequest implements Validatable, ToXContentObject {
Objects.equals(scope, that.scope) &&
Objects.equals(username, that.username) &&
Arrays.equals(password, that.password) &&
- Objects.equals(refreshToken, that.refreshToken);
+ Objects.equals(refreshToken, that.refreshToken) &&
+ Arrays.equals(kerberosTicket, that.kerberosTicket);
}
@Override
public int hashCode() {
int result = Objects.hash(grantType, scope, username, refreshToken);
result = 31 * result + Arrays.hashCode(password);
+ result = 31 * result + Arrays.hashCode(kerberosTicket);
return result;
}
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenRequestTests.java
index 53f3e1d0f36..760d5e52cb3 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenRequestTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/CreateTokenRequestTests.java
@@ -66,31 +66,54 @@ public class CreateTokenRequestTests extends ESTestCase {
assertThat(Strings.toString(request), equalTo("{\"grant_type\":\"client_credentials\"}"));
}
+ public void testCreateTokenFromKerberosTicket() {
+ final CreateTokenRequest request = CreateTokenRequest.kerberosGrant("top secret kerberos ticket".toCharArray());
+ assertThat(request.getGrantType(), equalTo("_kerberos"));
+ assertThat(request.getScope(), nullValue());
+ assertThat(request.getUsername(), nullValue());
+ assertThat(request.getPassword(), nullValue());
+ assertThat(request.getRefreshToken(), nullValue());
+ assertThat(new String(request.getKerberosTicket()), equalTo("top secret kerberos ticket"));
+ assertThat(Strings.toString(request), equalTo("{\"grant_type\":\"_kerberos\"," +
+ "\"kerberos_ticket\":\"top secret kerberos ticket\"}"));
+ }
+
public void testEqualsAndHashCode() {
final String grantType = randomAlphaOfLength(8);
final String scope = randomBoolean() ? null : randomAlphaOfLength(6);
final String username = randomBoolean() ? null : randomAlphaOfLengthBetween(4, 10);
final char[] password = randomBoolean() ? null : randomAlphaOfLengthBetween(8, 12).toCharArray();
final String refreshToken = randomBoolean() ? null : randomAlphaOfLengthBetween(12, 24);
- final CreateTokenRequest request = new CreateTokenRequest(grantType, scope, username, password, refreshToken);
+ final char[] kerberosTicket = randomBoolean() ? null : randomAlphaOfLengthBetween(8, 12).toCharArray();
+ final CreateTokenRequest request = new CreateTokenRequest(grantType, scope, username, password, refreshToken, kerberosTicket);
EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
- r -> new CreateTokenRequest(r.getGrantType(), r.getScope(), r.getUsername(), r.getPassword(), r.getRefreshToken()),
+ r -> new CreateTokenRequest(r.getGrantType(), r.getScope(), r.getUsername(), r.getPassword(),
+ r.getRefreshToken(), r.getKerberosTicket()),
this::mutate);
}
private CreateTokenRequest mutate(CreateTokenRequest req) {
- switch (randomIntBetween(1, 5)) {
- case 1:
- return new CreateTokenRequest("g", req.getScope(), req.getUsername(), req.getPassword(), req.getRefreshToken());
- case 2:
- return new CreateTokenRequest(req.getGrantType(), "s", req.getUsername(), req.getPassword(), req.getRefreshToken());
- case 3:
- return new CreateTokenRequest(req.getGrantType(), req.getScope(), "u", req.getPassword(), req.getRefreshToken());
- case 4:
- final char[] password = {'p'};
- return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), password, req.getRefreshToken());
- case 5:
- return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), req.getPassword(), "r");
+ switch (randomIntBetween(1, 6)) {
+ case 1:
+ return new CreateTokenRequest("g", req.getScope(), req.getUsername(), req.getPassword(), req.getRefreshToken(),
+ req.getKerberosTicket());
+ case 2:
+ return new CreateTokenRequest(req.getGrantType(), "s", req.getUsername(), req.getPassword(), req.getRefreshToken(),
+ req.getKerberosTicket());
+ case 3:
+ return new CreateTokenRequest(req.getGrantType(), req.getScope(), "u", req.getPassword(), req.getRefreshToken(),
+ req.getKerberosTicket());
+ case 4:
+ final char[] password = { 'p' };
+ return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), password, req.getRefreshToken(),
+ req.getKerberosTicket());
+ case 5:
+ final char[] kerberosTicket = { 'k' };
+ return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), req.getPassword(), req.getRefreshToken(),
+ kerberosTicket);
+ case 6:
+ return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), req.getPassword(), "r",
+ req.getKerberosTicket());
}
throw new IllegalStateException("Bad random number");
}
diff --git a/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
index 7a763c5a049..2e213cccf13 100644
--- a/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
+++ b/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
@@ -30,11 +30,13 @@ import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.CharsRef;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
+import org.elasticsearch.common.CharArrays;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoPoint;
+import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
@@ -59,6 +61,7 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -358,6 +361,21 @@ public abstract class StreamInput extends InputStream {
return null;
}
+ @Nullable
+ public SecureString readOptionalSecureString() throws IOException {
+ SecureString value = null;
+ BytesReference bytesRef = readOptionalBytesReference();
+ if (bytesRef != null) {
+ byte[] bytes = BytesReference.toBytes(bytesRef);
+ try {
+ value = new SecureString(CharArrays.utf8BytesToChars(bytes));
+ } finally {
+ Arrays.fill(bytes, (byte) 0);
+ }
+ }
+ return value;
+ }
+
@Nullable
public Float readOptionalFloat() throws IOException {
if (readBoolean()) {
@@ -415,6 +433,16 @@ public abstract class StreamInput extends InputStream {
return spare.toString();
}
+ public SecureString readSecureString() throws IOException {
+ BytesReference bytesRef = readBytesReference();
+ byte[] bytes = BytesReference.toBytes(bytesRef);
+ try {
+ return new SecureString(CharArrays.utf8BytesToChars(bytes));
+ } finally {
+ Arrays.fill(bytes, (byte) 0);
+ }
+ }
+
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
diff --git a/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
index 1c9dfd7ea44..702cf3313d7 100644
--- a/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
+++ b/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
@@ -32,10 +32,13 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.common.CharArrays;
import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.io.stream.Writeable.Writer;
+import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
@@ -58,6 +61,7 @@ import java.nio.file.NotDirectoryException;
import java.time.ZoneId;
import java.time.Instant;
import java.time.ZonedDateTime;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -324,6 +328,19 @@ public abstract class StreamOutput extends OutputStream {
}
}
+ public void writeOptionalSecureString(@Nullable SecureString secureStr) throws IOException {
+ if (secureStr == null) {
+ writeOptionalBytesReference(null);
+ } else {
+ final byte[] secureStrBytes = CharArrays.toUtf8Bytes(secureStr.getChars());
+ try {
+ writeOptionalBytesReference(new BytesArray(secureStrBytes));
+ } finally {
+ Arrays.fill(secureStrBytes, (byte) 0);
+ }
+ }
+ }
+
/**
* Writes an optional {@link Integer}.
*/
@@ -414,6 +431,15 @@ public abstract class StreamOutput extends OutputStream {
writeBytes(buffer, offset);
}
+ public void writeSecureString(SecureString secureStr) throws IOException {
+ final byte[] secureStrBytes = CharArrays.toUtf8Bytes(secureStr.getChars());
+ try {
+ writeBytesReference(new BytesArray(secureStrBytes));
+ } finally {
+ Arrays.fill(secureStrBytes, (byte) 0);
+ }
+ }
+
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
diff --git a/server/src/test/java/org/elasticsearch/common/io/stream/StreamTests.java b/server/src/test/java/org/elasticsearch/common/io/stream/StreamTests.java
index 837c0202faf..ce0bce03b03 100644
--- a/server/src/test/java/org/elasticsearch/common/io/stream/StreamTests.java
+++ b/server/src/test/java/org/elasticsearch/common/io/stream/StreamTests.java
@@ -25,6 +25,7 @@ import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.test.ESTestCase;
import java.io.ByteArrayInputStream;
@@ -49,7 +50,9 @@ import java.util.stream.IntStream;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasToString;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.iterableWithSize;
+import static org.hamcrest.Matchers.nullValue;
public class StreamTests extends ESTestCase {
@@ -405,4 +408,30 @@ public class StreamTests extends ESTestCase {
}
}
+ public void testSecureStringSerialization() throws IOException {
+ try (BytesStreamOutput output = new BytesStreamOutput()) {
+ final SecureString secureString = new SecureString("super secret".toCharArray());
+ output.writeSecureString(secureString);
+
+ final BytesReference bytesReference = output.bytes();
+ final StreamInput input = bytesReference.streamInput();
+
+ assertThat(secureString, is(equalTo(input.readSecureString())));
+ }
+
+ try (BytesStreamOutput output = new BytesStreamOutput()) {
+ final SecureString secureString = randomBoolean() ? null : new SecureString("super secret".toCharArray());
+ output.writeOptionalSecureString(secureString);
+
+ final BytesReference bytesReference = output.bytes();
+ final StreamInput input = bytesReference.streamInput();
+
+ if (secureString != null) {
+ assertThat(input.readOptionalSecureString(), is(equalTo(secureString)));
+ } else {
+ assertThat(input.readOptionalSecureString(), is(nullValue()));
+ }
+ }
+ }
+
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java
index fc671a833fa..85a518133ff 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequest.java
@@ -8,17 +8,13 @@ package org.elasticsearch.xpack.core.security.action.token;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
-import org.elasticsearch.common.CharArrays;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
-import org.elasticsearch.common.bytes.BytesArray;
-import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.SecureString;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Locale;
@@ -214,32 +210,23 @@ public final class CreateTokenRequest extends ActionRequest {
throw new IllegalArgumentException("a request with the client_credentials grant_type cannot be sent to version [" +
out.getVersion() + "]");
}
+ if (out.getVersion().before(Version.V_7_3_0) && GrantType.KERBEROS.getValue().equals(grantType)) {
+ throw new IllegalArgumentException("a request with the _kerberos grant_type cannot be sent to version [" +
+ out.getVersion() + "]");
+ }
out.writeString(grantType);
if (out.getVersion().onOrAfter(Version.V_6_2_0)) {
out.writeOptionalString(username);
- if (password == null) {
- out.writeOptionalBytesReference(null);
- } else {
- final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars());
- try {
- out.writeOptionalBytesReference(new BytesArray(passwordBytes));
- } finally {
- Arrays.fill(passwordBytes, (byte) 0);
- }
- }
+ out.writeOptionalSecureString(password);
out.writeOptionalString(refreshToken);
+ out.writeOptionalSecureString(kerberosTicket);
} else {
if ("refresh_token".equals(grantType)) {
throw new IllegalArgumentException("a refresh request cannot be sent to an older version");
} else {
out.writeString(username);
- final byte[] passwordBytes = CharArrays.toUtf8Bytes(password.getChars());
- try {
- out.writeByteArray(passwordBytes);
- } finally {
- Arrays.fill(passwordBytes, (byte) 0);
- }
+ out.writeSecureString(password);
}
}
out.writeOptionalString(scope);
@@ -251,26 +238,12 @@ public final class CreateTokenRequest extends ActionRequest {
grantType = in.readString();
if (in.getVersion().onOrAfter(Version.V_6_2_0)) {
username = in.readOptionalString();
- BytesReference bytesRef = in.readOptionalBytesReference();
- if (bytesRef != null) {
- byte[] bytes = BytesReference.toBytes(bytesRef);
- try {
- password = new SecureString(CharArrays.utf8BytesToChars(bytes));
- } finally {
- Arrays.fill(bytes, (byte) 0);
- }
- } else {
- password = null;
- }
+ password = in.readOptionalSecureString();
refreshToken = in.readOptionalString();
+ kerberosTicket = in.readOptionalSecureString();
} else {
username = in.readString();
- final byte[] passwordBytes = in.readByteArray();
- try {
- password = new SecureString(CharArrays.utf8BytesToChars(passwordBytes));
- } finally {
- Arrays.fill(passwordBytes, (byte) 0);
- }
+ password = in.readSecureString();
}
scope = in.readOptionalString();
}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java
index d5edbc8f1c3..54681e97fc5 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/CreateTokenRequestTests.java
@@ -6,8 +6,15 @@
package org.elasticsearch.xpack.core.security.action.token;
import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequest.GrantType;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
@@ -106,4 +113,35 @@ public class CreateTokenRequestTests extends ESTestCase {
ve = request.validate();
assertNull(ve);
}
+
+ public void testSerialization() throws IOException {
+ final String grantType = randomFrom(Arrays.stream(GrantType.values()).map(gt -> gt.getValue()).collect(Collectors.toList()));
+ final String username = randomBoolean() ? randomAlphaOfLength(5) : null;
+ final String scope = randomBoolean() ? randomAlphaOfLength(5) : null;
+ final SecureString password = randomBoolean() ? new SecureString(new char[] { 'p', 'a', 's', 's' }) : null;
+ final SecureString kerberosTicket = randomBoolean() ? new SecureString(new char[] { 'k', 'e', 'r', 'b' }) : null;
+ final String refreshToken = randomBoolean() ? randomAlphaOfLength(5) : null;
+ final CreateTokenRequest request = new CreateTokenRequest(grantType, username, password, kerberosTicket, scope, refreshToken);
+
+ try (BytesStreamOutput out = new BytesStreamOutput()) {
+ request.writeTo(out);
+ try (StreamInput in = out.bytes().streamInput()) {
+ final CreateTokenRequest serialized = new CreateTokenRequest();
+ serialized.readFrom(in);
+ assertEquals(grantType, serialized.getGrantType());
+ if (scope != null) {
+ assertEquals(scope, serialized.getScope());
+ }
+ if (password != null) {
+ assertEquals(password, serialized.getPassword());
+ }
+ if (kerberosTicket != null) {
+ assertEquals(kerberosTicket, serialized.getKerberosTicket());
+ }
+ if (refreshToken != null) {
+ assertEquals(refreshToken, serialized.getRefreshToken());
+ }
+ }
+ }
+ }
}