Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
caef646
main code and test fix
albertzaharovits Jul 3, 2019
cd92c33
PkiRealmTests
albertzaharovits Jul 3, 2019
f6296c5
Done
albertzaharovits Jul 3, 2019
82edac2
Checkstyle
albertzaharovits Jul 3, 2019
f2a74ec
Lower case logger
albertzaharovits Jul 3, 2019
e780361
Merge branch 'master' into fix-x509-token-principal
albertzaharovits Jul 3, 2019
80c8031
Remove PkiRealm.token
albertzaharovits Jul 7, 2019
de00ee2
Merge branch 'master' into fix-x509-token-principal
albertzaharovits Jul 7, 2019
bf41143
Main and DelegatePkiRequestTests
albertzaharovits Jul 8, 2019
5d11d14
DelegatePkiResponseTests
albertzaharovits Jul 8, 2019
4de0152
PkiRealmTests
albertzaharovits Jul 8, 2019
0e241dd
PkiAuthDelegationIntegTests
albertzaharovits Jul 9, 2019
6f4f11f
Parse principal before authentication for BWC
albertzaharovits Jul 9, 2019
176e5dc
Merge branch 'fix-x509-token-principal' into add-delegate-transport
albertzaharovits Jul 9, 2019
406f429
PkiRealmTests.testCustomUsernamePatternMismatchesAndFails
albertzaharovits Jul 9, 2019
399a8bb
Merge branch 'fix-x509-token-principal' into add-delegate-transport
albertzaharovits Jul 9, 2019
b49e354
Update x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/s…
albertzaharovits Jul 11, 2019
b16f889
Loggers
albertzaharovits Jul 11, 2019
6cbe7c4
Merge branch 'master' into fix-x509-token-principal
albertzaharovits Jul 11, 2019
e22d195
Merge branch 'fix-x509-token-principal' into add-delegate-transport
albertzaharovits Jul 11, 2019
53e68cd
Merge branch 'master' into add-delegate-transport
albertzaharovits Jul 11, 2019
c6c61d8
Rename allow_delegation to delegation.enabled
albertzaharovits Jul 11, 2019
43c47b8
Merge branch 'master' into add-delegate-transport
albertzaharovits Jul 12, 2019
2c149f2
Streamable is interface
albertzaharovits Jul 12, 2019
6f438b7
Validate certificate chain order
albertzaharovits Jul 15, 2019
475f4cc
delegation.enabled javadoc
albertzaharovits Jul 15, 2019
ea4bef1
Fix tests
albertzaharovits Jul 16, 2019
acef8e0
Javadoc nit-pick
albertzaharovits Jul 16, 2019
a0cfa1b
Update x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/s…
albertzaharovits Jul 16, 2019
9bc9591
Merge branch 'master' into security-pki-delegation-add-delegate-trans…
albertzaharovits Jul 16, 2019
60e481c
Checkstyle
albertzaharovits Jul 16, 2019
c117705
Review
albertzaharovits Jul 16, 2019
454527f
Renames
albertzaharovits Jul 16, 2019
8d03a05
Checkstyle
albertzaharovits Jul 16, 2019
01fceaf
Update docs/reference/settings/security-settings.asciidoc
albertzaharovits Jul 17, 2019
7203a1d
Update x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/…
albertzaharovits Jul 17, 2019
ec11504
Checkstyle
albertzaharovits Jul 17, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/reference/settings/security-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,14 @@ Defaults to `20m`.
Specifies the maximum number of user entries that the cache can contain.
Defaults to `100000`.

`delegation.enabled`::
Generally, in order for the clients to be authenticated by the PKI realm they
must connect directly to {es}. That is, they must not pass through proxies
which terminate the TLS connection. In order to allow for a *trusted* and
*smart* proxy, such as Kibana, to sit before {es} and terminate TLS
connections, but still allow clients to be authenticated on {es} by this realm,
you need to toggle this to `true`. Defaults to `false`.

[[ref-saml-settings]]
[float]
===== SAML realm settings
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.action;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import static org.elasticsearch.action.ValidateActions.addValidationError;

public final class DelegatePkiAuthenticationRequest extends ActionRequest {

private X509Certificate[] certificates;

public DelegatePkiAuthenticationRequest(X509Certificate[] certificates) {
this.certificates = certificates;
}

public DelegatePkiAuthenticationRequest(StreamInput in) throws IOException {
this.readFrom(in);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (certificates == null) {
validationException = addValidationError("certificates chain array must not be null", validationException);
} else if (certificates.length == 0) {
validationException = addValidationError("certificates chain array must not be empty", validationException);
} else if (false == CertParsingUtils.isOrderedCertificateChain(certificates)) {
validationException = addValidationError("certificates chain array is not ordered", validationException);
}
return validationException;
}

public X509Certificate[] getCertificates() {
return certificates;
}

@Override
public void readFrom(StreamInput input) throws IOException {
super.readFrom(input);
try {
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
certificates = input.readArray(in -> {
try (ByteArrayInputStream bis = new ByteArrayInputStream(in.readByteArray())) {
return (X509Certificate) certificateFactory.generateCertificate(bis);
} catch (CertificateException e) {
throw new IOException(e);
}
}, X509Certificate[]::new);
} catch (CertificateException e) {
throw new IOException(e);
}
}

@Override
public void writeTo(StreamOutput output) throws IOException {
super.writeTo(output);
output.writeArray((out, cert) -> {
try {
out.writeByteArray(cert.getEncoded());
} catch (CertificateEncodingException e) {
throw new IOException(e);
}
}, certificates);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DelegatePkiAuthenticationRequest that = (DelegatePkiAuthenticationRequest) o;
return Arrays.equals(certificates, that.certificates);
}

@Override
public int hashCode() {
return Arrays.hashCode(certificates);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.action;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;

import java.io.IOException;
import java.util.Objects;

public final class DelegatePkiAuthenticationResponse extends ActionResponse {

private String tokenString;
private TimeValue expiresIn;

DelegatePkiAuthenticationResponse() { }

public DelegatePkiAuthenticationResponse(String tokenString, TimeValue expiresIn) {
this.tokenString = Objects.requireNonNull(tokenString);
this.expiresIn = Objects.requireNonNull(expiresIn);
}

public DelegatePkiAuthenticationResponse(StreamInput input) throws IOException {
this.readFrom(input);
}

public String getTokenString() {
return tokenString;
}

public TimeValue getExpiresIn() {
return expiresIn;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(tokenString);
out.writeTimeValue(expiresIn);
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
tokenString = in.readString();
expiresIn = in.readTimeValue();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DelegatePkiAuthenticationResponse that = (DelegatePkiAuthenticationResponse) o;
return Objects.equals(tokenString, that.tokenString) &&
Objects.equals(expiresIn, that.expiresIn);
}

@Override
public int hashCode() {
return Objects.hash(tokenString, expiresIn);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public final class PkiRealmSettings {
RealmSettings.realmSettingPrefix(TYPE), "cache.max_users",
key -> Setting.intSetting(key, DEFAULT_MAX_USERS, Setting.Property.NodeScope));

public static final Setting.AffixSetting<Boolean> DELEGATION_ENABLED_SETTING = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "delegation.enabled",
key -> Setting.boolSetting(key, false, Setting.Property.NodeScope));

public static final Setting.AffixSetting<Optional<String>> TRUST_STORE_PATH;
public static final Setting.AffixSetting<Optional<String>> TRUST_STORE_TYPE;
public static final Setting.AffixSetting<SecureString> TRUST_STORE_PASSWORD;
Expand Down Expand Up @@ -72,6 +76,7 @@ public static Set<Setting.AffixSetting<?>> getSettings() {
settings.add(USERNAME_PATTERN_SETTING);
settings.add(CACHE_TTL_SETTING);
settings.add(CACHE_MAX_USERS_SETTING);
settings.add(DELEGATION_ENABLED_SETTING);

settings.add(TRUST_STORE_PATH);
settings.add(TRUST_STORE_PASSWORD);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
Expand Down Expand Up @@ -275,4 +276,20 @@ public static X509ExtendedTrustManager trustManager(KeyStore keyStore, String al
}
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
}

/**
* Checks that the {@code X509Certificate} array is ordered, such that the end-entity certificate is first and it is followed by any
* certificate authorities'. The check validates that the {@code issuer} of every certificate is the {@code subject} of the certificate
* in the next array position. No other certificate attributes are checked.
*/
public static boolean isOrderedCertificateChain(X509Certificate[] chain) {
for (int i = 1; i < chain.length; i++) {
X509Certificate cert = chain[i - 1];
X509Certificate issuer = chain[i];
if (false == cert.getIssuerX500Principal().equals(issuer.getSubjectX500Principal())) {
return false;
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.action;

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.security.auth.x500.X500Principal;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.arrayContaining;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class DelegatePkiAuthenticationRequestTests extends ESTestCase {

public void testRequestValidation() {
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(((X509Certificate[]) null));
ActionRequestValidationException ve = request.validate();
assertNotNull(ve);
assertEquals(1, ve.validationErrors().size());
assertThat(ve.validationErrors().get(0), is("certificates chain array must not be null"));

request = new DelegatePkiAuthenticationRequest(new X509Certificate[0]);
ve = request.validate();
assertNotNull(ve);
assertEquals(1, ve.validationErrors().size());
assertThat(ve.validationErrors().get(0), is("certificates chain array must not be empty"));

X509Certificate[] mockCertChain = new X509Certificate[2];
mockCertChain[0] = mock(X509Certificate.class);
when(mockCertChain[0].getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
mockCertChain[1] = mock(X509Certificate.class);
when(mockCertChain[1].getSubjectX500Principal()).thenReturn(new X500Principal("CN=Not Test, OU=elasticsearch, O=org"));
request = new DelegatePkiAuthenticationRequest(mockCertChain);
ve = request.validate();
assertNotNull(ve);
assertEquals(1, ve.validationErrors().size());
assertThat(ve.validationErrors().get(0), is("certificates chain array is not ordered"));

request = new DelegatePkiAuthenticationRequest(randomArray(1, 3, X509Certificate[]::new, () -> {
X509Certificate mockX509Certificate = mock(X509Certificate.class);
when(mockX509Certificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
when(mockX509Certificate.getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
return mockX509Certificate;
}));
ve = request.validate();
assertNull(ve);
}

public void testSerialization() throws Exception {
X509Certificate[] certificates = randomArray(1, 3, X509Certificate[]::new, () -> {
try {
return readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/"
+ randomFrom("testclient.crt", "testnode.crt", "testnode-ip-only.crt", "openldap.crt", "samba4.crt")));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(certificates);

try (BytesStreamOutput out = new BytesStreamOutput()) {
request.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
final DelegatePkiAuthenticationRequest serialized = new DelegatePkiAuthenticationRequest(in);
assertThat(request.getCertificates(), arrayContaining(certificates));
assertThat(request, is(serialized));
assertThat(request.hashCode(), is(serialized.hashCode()));
}
}
}

static X509Certificate readCert(Path path) throws Exception {
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.action;

import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse;

import static org.hamcrest.Matchers.is;

public class DelegatePkiAuthenticationResponseTests extends ESTestCase {

public void testSerialization() throws Exception {
DelegatePkiAuthenticationResponse response = new DelegatePkiAuthenticationResponse(randomAlphaOfLengthBetween(0, 10),
TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn"));
try (BytesStreamOutput output = new BytesStreamOutput()) {
response.writeTo(output);
try (StreamInput input = output.bytes().streamInput()) {
DelegatePkiAuthenticationResponse serialized = new DelegatePkiAuthenticationResponse(input);
assertThat(response.getTokenString(), is(serialized.getTokenString()));
assertThat(response.getExpiresIn(), is(serialized.getExpiresIn()));
assertThat(response, is(serialized));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
import org.elasticsearch.xpack.core.ssl.action.TransportGetCertificateInfoAction;
import org.elasticsearch.xpack.core.ssl.rest.RestGetCertificateInfoAction;
import org.elasticsearch.xpack.security.action.TransportCreateApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportDelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.security.action.TransportGetApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
Expand Down Expand Up @@ -729,6 +730,7 @@ public void onIndexModule(IndexModule module) {
new ActionHandler<>(CreateApiKeyAction.INSTANCE, TransportCreateApiKeyAction.class),
new ActionHandler<>(InvalidateApiKeyAction.INSTANCE, TransportInvalidateApiKeyAction.class),
new ActionHandler<>(GetApiKeyAction.INSTANCE, TransportGetApiKeyAction.class),
new ActionHandler<>(TransportDelegatePkiAuthenticationAction.TYPE, TransportDelegatePkiAuthenticationAction.class),
usageAction,
infoAction);
}
Expand Down
Loading