Skip to content

Commit e5859fd

Browse files
authored
OIDC realm authentication flows (#37787)
Adds implementation for authentication in the OpenID Connect realm using the authorization code grant and the implicit flows as described in https://openid.net/specs/openid-connect-core-1_0.html
1 parent 7d89ce3 commit e5859fd

File tree

41 files changed

+4704
-220
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4704
-220
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectAuthenticateRequest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ public class OpenIdConnectAuthenticateRequest extends ActionRequest {
2424
private String redirectUri;
2525

2626
/**
27-
* The state value that we generated for this specific flow and that should be stored at the user's session with
28-
* the facilitator
27+
* The state value that we generated or the facilitator provided for this specific flow and that should be stored at the user's session
28+
* with the facilitator
2929
*/
3030
private String state;
3131

3232
/**
33-
* The nonce value that we generated for this specific flow and that should be stored at the user's session with
34-
* the facilitator
33+
* The nonce value that we generated or the facilitator provided for this specific flow and that should be stored at the user's session
34+
* with the facilitator
3535
*/
3636
private String nonce;
3737

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectPrepareAuthenticationRequest.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,41 @@
2121
public class OpenIdConnectPrepareAuthenticationRequest extends ActionRequest {
2222

2323
private String realmName;
24+
private String state;
25+
private String nonce;
2426

2527
public String getRealmName() {
2628
return realmName;
2729
}
2830

31+
public String getState() {
32+
return state;
33+
}
34+
35+
public String getNonce() {
36+
return nonce;
37+
}
38+
2939
public void setRealmName(String realmName) {
3040
this.realmName = realmName;
3141
}
3242

43+
public void setState(String state) {
44+
this.state = state;
45+
}
46+
47+
public void setNonce(String nonce) {
48+
this.nonce = nonce;
49+
}
50+
3351
public OpenIdConnectPrepareAuthenticationRequest() {
3452
}
3553

3654
public OpenIdConnectPrepareAuthenticationRequest(StreamInput in) throws IOException {
3755
super.readFrom(in);
3856
realmName = in.readString();
57+
state = in.readOptionalString();
58+
nonce = in.readOptionalString();
3959
}
4060

4161
@Override
@@ -51,6 +71,8 @@ public ActionRequestValidationException validate() {
5171
public void writeTo(StreamOutput out) throws IOException {
5272
super.writeTo(out);
5373
out.writeString(realmName);
74+
out.writeOptionalString(state);
75+
out.writeOptionalString(nonce);
5476
}
5577

5678
@Override
@@ -59,7 +81,7 @@ public void readFrom(StreamInput in) {
5981
}
6082

6183
public String toString() {
62-
return "{realmName=" + realmName + "}";
84+
return "{realmName=" + realmName + ", state=" + state + ", nonce=" + nonce + "}";
6385
}
6486

6587
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/oidc/OpenIdConnectPrepareAuthenticationResponse.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public String toString() {
7474
@Override
7575
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
7676
builder.startObject();
77-
builder.field("authentication_request_url", authenticationRequestUrl);
77+
builder.field("redirect", authenticationRequestUrl);
7878
builder.field("state", state);
7979
builder.field("nonce", nonce);
8080
builder.endObject();

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/oidc/OpenIdConnectRealmSettings.java

+144-12
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@
77

88
import org.elasticsearch.common.settings.SecureString;
99
import org.elasticsearch.common.settings.Setting;
10+
import org.elasticsearch.common.unit.TimeValue;
1011
import org.elasticsearch.common.util.set.Sets;
12+
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
1113
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
1214
import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings;
15+
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
1316

17+
import java.net.URI;
18+
import java.net.URISyntaxException;
19+
import java.util.Arrays;
20+
import java.util.Collection;
1421
import java.util.Collections;
1522
import java.util.List;
1623
import java.util.Set;
@@ -22,36 +29,161 @@ public class OpenIdConnectRealmSettings {
2229
private OpenIdConnectRealmSettings() {
2330
}
2431

32+
private static final List<String> signatureAlgorithms = Collections.unmodifiableList(
33+
Arrays.asList("HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"));
34+
private static final List<String> responseTypes = Arrays.asList("code", "id_token", "id_token token");
2535
public static final String TYPE = "oidc";
2636

27-
public static final Setting.AffixSetting<String> OP_NAME
28-
= RealmSettings.simpleString(TYPE, "op.name", Setting.Property.NodeScope);
2937
public static final Setting.AffixSetting<String> RP_CLIENT_ID
3038
= RealmSettings.simpleString(TYPE, "rp.client_id", Setting.Property.NodeScope);
3139
public static final Setting.AffixSetting<SecureString> RP_CLIENT_SECRET
3240
= RealmSettings.secureString(TYPE, "rp.client_secret");
3341
public static final Setting.AffixSetting<String> RP_REDIRECT_URI
34-
= RealmSettings.simpleString(TYPE, "rp.redirect_uri", Setting.Property.NodeScope);
42+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.redirect_uri",
43+
key -> Setting.simpleString(key, v -> {
44+
try {
45+
new URI(v);
46+
} catch (URISyntaxException e) {
47+
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
48+
}
49+
}, Setting.Property.NodeScope));
3550
public static final Setting.AffixSetting<String> RP_RESPONSE_TYPE
36-
= RealmSettings.simpleString(TYPE, "rp.response_type", Setting.Property.NodeScope);
51+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.response_type",
52+
key -> Setting.simpleString(key, v -> {
53+
if (responseTypes.contains(v) == false) {
54+
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Allowed values are " + responseTypes + "");
55+
}
56+
}, Setting.Property.NodeScope));
57+
public static final Setting.AffixSetting<String> RP_SIGNATURE_ALGORITHM
58+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.signature_algorithm",
59+
key -> new Setting<>(key, "RS256", Function.identity(), v -> {
60+
if (signatureAlgorithms.contains(v) == false) {
61+
throw new IllegalArgumentException(
62+
"Invalid value [" + v + "] for [" + key + "]. Allowed values are " + signatureAlgorithms + "}]");
63+
}
64+
}, Setting.Property.NodeScope));
65+
public static final Setting.AffixSetting<List<String>> RP_REQUESTED_SCOPES = Setting.affixKeySetting(
66+
RealmSettings.realmSettingPrefix(TYPE), "rp.requested_scopes",
67+
key -> Setting.listSetting(key, Collections.singletonList("openid"), Function.identity(), Setting.Property.NodeScope));
68+
69+
public static final Setting.AffixSetting<String> OP_NAME
70+
= RealmSettings.simpleString(TYPE, "op.name", Setting.Property.NodeScope);
3771
public static final Setting.AffixSetting<String> OP_AUTHORIZATION_ENDPOINT
38-
= RealmSettings.simpleString(TYPE, "op.authorization_endpoint", Setting.Property.NodeScope);
72+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.authorization_endpoint",
73+
key -> Setting.simpleString(key, v -> {
74+
try {
75+
new URI(v);
76+
} catch (URISyntaxException e) {
77+
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
78+
}
79+
}, Setting.Property.NodeScope));
3980
public static final Setting.AffixSetting<String> OP_TOKEN_ENDPOINT
40-
= RealmSettings.simpleString(TYPE, "op.token_endpoint", Setting.Property.NodeScope);
81+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.token_endpoint",
82+
key -> Setting.simpleString(key, v -> {
83+
try {
84+
new URI(v);
85+
} catch (URISyntaxException e) {
86+
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
87+
}
88+
}, Setting.Property.NodeScope));
4189
public static final Setting.AffixSetting<String> OP_USERINFO_ENDPOINT
42-
= RealmSettings.simpleString(TYPE, "op.userinfo_endpoint", Setting.Property.NodeScope);
90+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.userinfo_endpoint",
91+
key -> Setting.simpleString(key, v -> {
92+
try {
93+
new URI(v);
94+
} catch (URISyntaxException e) {
95+
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
96+
}
97+
}, Setting.Property.NodeScope));
4398
public static final Setting.AffixSetting<String> OP_ISSUER
4499
= RealmSettings.simpleString(TYPE, "op.issuer", Setting.Property.NodeScope);
45-
public static final Setting.AffixSetting<List<String>> RP_REQUESTED_SCOPES = Setting.affixKeySetting(
46-
RealmSettings.realmSettingPrefix(TYPE), "rp.requested_scopes",
47-
key -> Setting.listSetting(key, Collections.singletonList("openid"), Function.identity(), Setting.Property.NodeScope));
100+
public static final Setting.AffixSetting<String> OP_JWKSET_PATH
101+
= RealmSettings.simpleString(TYPE, "op.jwkset_path", Setting.Property.NodeScope);
102+
103+
public static final Setting.AffixSetting<TimeValue> ALLOWED_CLOCK_SKEW
104+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "allowed_clock_skew",
105+
key -> Setting.timeSetting(key, TimeValue.timeValueSeconds(60), Setting.Property.NodeScope));
106+
public static final Setting.AffixSetting<Boolean> POPULATE_USER_METADATA = Setting.affixKeySetting(
107+
RealmSettings.realmSettingPrefix(TYPE), "populate_user_metadata",
108+
key -> Setting.boolSetting(key, true, Setting.Property.NodeScope));
109+
private static final TimeValue DEFAULT_TIMEOUT = TimeValue.timeValueSeconds(5);
110+
public static final Setting.AffixSetting<TimeValue> HTTP_CONNECT_TIMEOUT
111+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.connect_timeout",
112+
key -> Setting.timeSetting(key, DEFAULT_TIMEOUT, Setting.Property.NodeScope));
113+
public static final Setting.AffixSetting<TimeValue> HTTP_CONNECTION_READ_TIMEOUT
114+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.connection_read_timeout",
115+
key -> Setting.timeSetting(key, DEFAULT_TIMEOUT, Setting.Property.NodeScope));
116+
public static final Setting.AffixSetting<TimeValue> HTTP_SOCKET_TIMEOUT
117+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.socket_timeout",
118+
key -> Setting.timeSetting(key, DEFAULT_TIMEOUT, Setting.Property.NodeScope));
119+
public static final Setting.AffixSetting<Integer> HTTP_MAX_CONNECTIONS
120+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.max_connections",
121+
key -> Setting.intSetting(key, 200, Setting.Property.NodeScope));
122+
public static final Setting.AffixSetting<Integer> HTTP_MAX_ENDPOINT_CONNECTIONS
123+
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "http.max_endpoint_connections",
124+
key -> Setting.intSetting(key, 200, Setting.Property.NodeScope));
125+
126+
public static final ClaimSetting PRINCIPAL_CLAIM = new ClaimSetting("principal");
127+
public static final ClaimSetting GROUPS_CLAIM = new ClaimSetting("groups");
128+
public static final ClaimSetting NAME_CLAIM = new ClaimSetting("name");
129+
public static final ClaimSetting DN_CLAIM = new ClaimSetting("dn");
130+
public static final ClaimSetting MAIL_CLAIM = new ClaimSetting("mail");
48131

49132
public static Set<Setting.AffixSetting<?>> getSettings() {
50133
final Set<Setting.AffixSetting<?>> set = Sets.newHashSet(
51-
OP_NAME, RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET,
52-
OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ISSUER);
134+
RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET, RP_SIGNATURE_ALGORITHM,
135+
OP_NAME, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH,
136+
HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT, HTTP_SOCKET_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS,
137+
ALLOWED_CLOCK_SKEW);
53138
set.addAll(DelegatedAuthorizationSettings.getSettings(TYPE));
54139
set.addAll(RealmSettings.getStandardSettings(TYPE));
140+
set.addAll(SSLConfigurationSettings.getRealmSettings(TYPE));
141+
set.addAll(PRINCIPAL_CLAIM.settings());
142+
set.addAll(GROUPS_CLAIM.settings());
143+
set.addAll(DN_CLAIM.settings());
144+
set.addAll(NAME_CLAIM.settings());
145+
set.addAll(MAIL_CLAIM.settings());
55146
return set;
56147
}
148+
149+
/**
150+
* The OIDC realm offers a number of settings that rely on claim values that are populated by the OP in the ID Token or the User Info
151+
* response.
152+
* Each claim has 2 settings:
153+
* <ul>
154+
* <li>The name of the OpenID Connect claim to use</li>
155+
* <li>An optional java pattern (regex) to apply to that claim value in order to extract the substring that should be used.</li>
156+
* </ul>
157+
* For example, the Elasticsearch User Principal could be configured to come from the OpenID Connect standard claim "email",
158+
* and extract only the local-part of the user's email address (i.e. the name before the '@').
159+
* This class encapsulates those 2 settings.
160+
*/
161+
public static final class ClaimSetting {
162+
public static final String CLAIMS_PREFIX = "claims.";
163+
public static final String CLAIM_PATTERNS_PREFIX = "claim_patterns.";
164+
165+
private final Setting.AffixSetting<String> claim;
166+
private final Setting.AffixSetting<String> pattern;
167+
168+
public ClaimSetting(String name) {
169+
claim = RealmSettings.simpleString(TYPE, CLAIMS_PREFIX + name, Setting.Property.NodeScope);
170+
pattern = RealmSettings.simpleString(TYPE, CLAIM_PATTERNS_PREFIX + name, Setting.Property.NodeScope);
171+
}
172+
173+
public Collection<Setting.AffixSetting<?>> settings() {
174+
return Arrays.asList(getClaim(), getPattern());
175+
}
176+
177+
public String name(RealmConfig config) {
178+
return getClaim().getConcreteSettingForNamespace(config.name()).getKey();
179+
}
180+
181+
public Setting.AffixSetting<String> getClaim() {
182+
return claim;
183+
}
184+
185+
public Setting.AffixSetting<String> getPattern() {
186+
return pattern;
187+
}
188+
}
57189
}

x-pack/plugin/security/build.gradle

+25-3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ dependencies {
5656
compile "org.apache.httpcomponents:httpclient-cache:${versions.httpclient}"
5757
compile 'com.google.guava:guava:19.0'
5858

59+
// Dependencies for oidc
60+
compile "com.nimbusds:oauth2-oidc-sdk:6.5"
61+
compile "com.nimbusds:nimbus-jose-jwt:4.41.2"
62+
compile "com.nimbusds:lang-tag:1.4.4"
63+
compile "com.sun.mail:javax.mail:1.6.2"
64+
compile "net.jcip:jcip-annotations:1.0"
65+
compile "net.minidev:json-smart:2.3"
66+
compile "net.minidev:accessors-smart:1.2"
67+
68+
5969
testCompile 'org.elasticsearch:securemock:1.2'
6070
testCompile "org.elasticsearch:mocksocket:${versions.mocksocket}"
6171
//testCompile "org.yaml:snakeyaml:${versions.snakeyaml}"
@@ -160,7 +170,7 @@ forbiddenPatterns {
160170
}
161171

162172
forbiddenApisMain {
163-
signaturesFiles += files('forbidden/ldap-signatures.txt', 'forbidden/xml-signatures.txt')
173+
signaturesFiles += files('forbidden/ldap-signatures.txt', 'forbidden/xml-signatures.txt', 'forbidden/oidc-signatures.txt')
164174
}
165175

166176
// classes are missing, e.g. com.ibm.icu.lang.UCharacter
@@ -257,7 +267,13 @@ thirdPartyAudit {
257267
'net.sf.ehcache.Ehcache',
258268
'net.sf.ehcache.Element',
259269
// [missing classes] SLF4j includes an optional class that depends on an extension class (!)
260-
'org.slf4j.ext.EventData'
270+
'org.slf4j.ext.EventData',
271+
'org.cryptomator.siv.SivMode',
272+
'org.objectweb.asm.ClassWriter',
273+
'org.objectweb.asm.Label',
274+
'org.objectweb.asm.MethodVisitor',
275+
'org.objectweb.asm.Type'
276+
261277
)
262278

263279
ignoreViolations (
@@ -278,7 +294,13 @@ if (project.runtimeJavaVersion > JavaVersion.VERSION_1_8) {
278294
'javax.xml.bind.JAXBElement',
279295
'javax.xml.bind.JAXBException',
280296
'javax.xml.bind.Unmarshaller',
281-
'javax.xml.bind.UnmarshallerHandler'
297+
'javax.xml.bind.UnmarshallerHandler',
298+
'javax.activation.ActivationDataFlavor',
299+
'javax.activation.DataContentHandler',
300+
'javax.activation.DataHandler',
301+
'javax.activation.DataSource',
302+
'javax.activation.FileDataSource',
303+
'javax.activation.FileTypeMap'
282304
)
283305
}
284306

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@defaultMessage Blocking methods should not be used for HTTP requests. Use CloseableHttpAsyncClient instead
2+
com.nimbusds.oauth2.sdk.http.HTTPRequest#send(javax.net.ssl.HostnameVerifier, javax.net.ssl.SSLSocketFactory)
3+
com.nimbusds.oauth2.sdk.http.HTTPRequest#send()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c592b500269bfde36096641b01238a8350f8aa31

0 commit comments

Comments
 (0)