Skip to content

Commit b076e6c

Browse files
committed
Adding API for generating SAML SP metadata
Resolves elastic#49018
1 parent 82f663f commit b076e6c

File tree

5 files changed

+46
-41
lines changed

5 files changed

+46
-41
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.xpack.core.ilm.action.StopILMAction;
2323
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
2424
import org.elasticsearch.xpack.core.security.action.GrantApiKeyAction;
25+
import org.elasticsearch.xpack.core.security.action.saml.SamlSpMetadataAction;
2526
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
2627
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
2728
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
@@ -45,7 +46,7 @@ public class ClusterPrivilegeResolver {
4546
// shared automatons
4647
private static final Set<String> ALL_SECURITY_PATTERN = Set.of("cluster:admin/xpack/security/*");
4748
private static final Set<String> MANAGE_SAML_PATTERN = Set.of("cluster:admin/xpack/security/saml/*",
48-
InvalidateTokenAction.NAME, RefreshTokenAction.NAME);
49+
InvalidateTokenAction.NAME, RefreshTokenAction.NAME, SamlSpMetadataAction.NAME);
4950
private static final Set<String> MANAGE_OIDC_PATTERN = Set.of("cluster:admin/xpack/security/oidc/*");
5051
private static final Set<String> MANAGE_TOKEN_PATTERN = Set.of("cluster:admin/xpack/security/token/*");
5152
private static final Set<String> MANAGE_API_KEY_PATTERN = Set.of("cluster:admin/xpack/security/api_key/*");

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlMetadataCommand.java

+37-37
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,22 @@ public SamlMetadataCommand(CheckedFunction<Environment, KeyStoreWrapper, Excepti
110110
attributeSpec = parser.accepts("attribute", "additional SAML attributes to request").withRequiredArg();
111111
orgNameSpec = parser.accepts("organisation-name", "the name of the organisation operating this service").withRequiredArg();
112112
orgDisplayNameSpec = parser.accepts("organisation-display-name", "the display-name of the organisation operating this service")
113-
.availableIf(orgNameSpec).withRequiredArg();
113+
.availableIf(orgNameSpec).withRequiredArg();
114114
orgUrlSpec = parser.accepts("organisation-url", "the URL of the organisation operating this service")
115-
.requiredIf(orgNameSpec).withRequiredArg();
115+
.requiredIf(orgNameSpec).withRequiredArg();
116116
contactsSpec = parser.accepts("contacts", "Include contact information in metadata").availableUnless(batchSpec);
117117
signingPkcs12PathSpec = parser.accepts("signing-bundle", "path to an existing key pair (in PKCS#12 format) to be used for " +
118-
"signing ")
119-
.withRequiredArg();
118+
"signing ")
119+
.withRequiredArg();
120120
signingCertPathSpec = parser.accepts("signing-cert", "path to an existing signing certificate")
121-
.availableUnless(signingPkcs12PathSpec)
122-
.withRequiredArg();
121+
.availableUnless(signingPkcs12PathSpec)
122+
.withRequiredArg();
123123
signingKeyPathSpec = parser.accepts("signing-key", "path to an existing signing private key")
124-
.availableIf(signingCertPathSpec)
125-
.requiredIf(signingCertPathSpec)
126-
.withRequiredArg();
124+
.availableIf(signingCertPathSpec)
125+
.requiredIf(signingCertPathSpec)
126+
.withRequiredArg();
127127
keyPasswordSpec = parser.accepts("signing-key-password", "password for an existing signing private key or keypair")
128-
.withOptionalArg();
128+
.withOptionalArg();
129129
this.keyStoreFunction = keyStoreFunction;
130130
}
131131

@@ -159,19 +159,19 @@ EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Env
159159
final RealmConfig realm = findRealm(terminal, options, env);
160160
final Settings realmSettings = realm.settings().getByPrefix(RealmSettings.realmSettingPrefix(realm.identifier()));
161161
terminal.println(Terminal.Verbosity.VERBOSE,
162-
"Using realm configuration\n=====\n" + realmSettings.toDelimitedString('\n') + "=====");
162+
"Using realm configuration\n=====\n" + realmSettings.toDelimitedString('\n') + "=====");
163163
final Locale locale = findLocale(options);
164164
terminal.println(Terminal.Verbosity.VERBOSE, "Using locale: " + locale.toLanguageTag());
165165

166166
final SpConfiguration spConfig = SamlRealm.getSpConfiguration(realm);
167167
final SamlSpMetadataBuilder builder = new SamlSpMetadataBuilder(locale, spConfig.getEntityId())
168-
.assertionConsumerServiceUrl(spConfig.getAscUrl())
169-
.singleLogoutServiceUrl(spConfig.getLogoutUrl())
170-
.encryptionCredentials(spConfig.getEncryptionCredentials())
171-
.signingCredential(spConfig.getSigningConfiguration().getCredential())
172-
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
173-
.nameIdFormat(realm.getSetting(SamlRealmSettings.NAMEID_FORMAT))
174-
.serviceName(option(serviceNameSpec, options, env.settings().get("cluster.name")));
168+
.assertionConsumerServiceUrl(spConfig.getAscUrl())
169+
.singleLogoutServiceUrl(spConfig.getLogoutUrl())
170+
.encryptionCredentials(spConfig.getEncryptionCredentials())
171+
.signingCredential(spConfig.getSigningConfiguration().getCredential())
172+
.authnRequestsSigned(spConfig.getSigningConfiguration().shouldSign(AuthnRequest.DEFAULT_ELEMENT_LOCAL_NAME))
173+
.nameIdFormat(realm.getSetting(SamlRealmSettings.NAMEID_FORMAT))
174+
.serviceName(option(serviceNameSpec, options, env.settings().get("cluster.name")));
175175

176176
Map<String, String> attributes = getAttributeNames(options, realm);
177177
for (String attr : attributes.keySet()) {
@@ -185,22 +185,22 @@ EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Env
185185
friendlyName = settingName;
186186
} else {
187187
friendlyName = terminal.readText("What is the friendly name for " +
188-
attributeSource
189-
+ " attribute \"" + attr + "\" [default: " +
190-
(settingName == null ? "none" : settingName) +
191-
"] ");
188+
attributeSource
189+
+ " attribute \"" + attr + "\" [default: " +
190+
(settingName == null ? "none" : settingName) +
191+
"] ");
192192
if (Strings.isNullOrEmpty(friendlyName)) {
193193
friendlyName = settingName;
194194
}
195195
}
196196
} else {
197197
if (batch) {
198198
throw new UserException(ExitCodes.CONFIG, "Option " + batchSpec.toString() + " is specified, but attribute "
199-
+ attr + " appears to be a FriendlyName value");
199+
+ attr + " appears to be a FriendlyName value");
200200
}
201201
friendlyName = attr;
202202
name = requireText(terminal,
203-
"What is the standard (urn) name for " + attributeSource + " attribute \"" + attr + "\" (required): ");
203+
"What is the standard (urn) name for " + attributeSource + " attribute \"" + attr + "\" (required): ");
204204
}
205205
terminal.println(Terminal.Verbosity.VERBOSE, "Requesting attribute '" + name + "' (FriendlyName: '" + friendlyName + "')");
206206
builder.withAttribute(friendlyName, name);
@@ -225,7 +225,7 @@ EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Env
225225
break;
226226
} else {
227227
terminal.errorPrintln("Type '" + type + "' is not valid. Valid values are "
228-
+ Strings.collectionToCommaDelimitedString(ContactInfo.TYPES.keySet()));
228+
+ Strings.collectionToCommaDelimitedString(ContactInfo.TYPES.keySet()));
229229
}
230230
}
231231
builder.withContact(type, givenName, surName, email);
@@ -237,13 +237,13 @@ EntityDescriptor buildEntityDescriptor(Terminal terminal, OptionSet options, Env
237237

238238
// package-protected for testing
239239
Element possiblySignDescriptor(Terminal terminal, OptionSet options, EntityDescriptor descriptor, Environment env)
240-
throws UserException {
240+
throws UserException {
241241
try {
242242
final EntityDescriptorMarshaller marshaller = new EntityDescriptorMarshaller();
243243
if (options.has(signingPkcs12PathSpec) || (options.has(signingCertPathSpec) && options.has(signingKeyPathSpec))) {
244244
Signature signature = (Signature) XMLObjectProviderRegistrySupport.getBuilderFactory()
245-
.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
246-
.buildObject(Signature.DEFAULT_ELEMENT_NAME);
245+
.getBuilder(Signature.DEFAULT_ELEMENT_NAME)
246+
.buildObject(Signature.DEFAULT_ELEMENT_NAME);
247247
signature.setSigningCredential(buildSigningCredential(terminal, options, env));
248248
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
249249
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
@@ -279,18 +279,18 @@ private Path writeOutput(Terminal terminal, OptionSet options, Element element)
279279
}
280280

281281
private Credential buildSigningCredential(Terminal terminal, OptionSet options, Environment env) throws
282-
Exception {
282+
Exception {
283283
X509Certificate signingCertificate;
284284
PrivateKey signingKey;
285285
char[] password = getChars(keyPasswordSpec.value(options));
286286
if (options.has(signingPkcs12PathSpec)) {
287287
Path p12Path = resolvePath(signingPkcs12PathSpec.value(options));
288288
Map<Certificate, Key> keys = withPassword("certificate bundle (" + p12Path + ")", password,
289-
terminal, keyPassword -> CertParsingUtils.readPkcs12KeyPairs(p12Path, keyPassword, a -> keyPassword));
289+
terminal, keyPassword -> CertParsingUtils.readPkcs12KeyPairs(p12Path, keyPassword, a -> keyPassword));
290290

291291
if (keys.size() != 1) {
292292
throw new IllegalArgumentException("expected a single key in file [" + p12Path.toAbsolutePath() + "] but found [" +
293-
keys.size() + "]");
293+
keys.size() + "]");
294294
}
295295
final Map.Entry<Certificate, Key> pair = keys.entrySet().iterator().next();
296296
signingCertificate = (X509Certificate) pair.getKey();
@@ -302,7 +302,7 @@ private Credential buildSigningCredential(Terminal terminal, OptionSet options,
302302
Certificate[] certificates = CertParsingUtils.readCertificates(Collections.singletonList(resolvedSigningCertPath), env);
303303
if (certificates.length != 1) {
304304
throw new IllegalArgumentException("expected a single certificate in file [" + resolvedSigningCertPath + "] but found [" +
305-
certificates.length + "]");
305+
certificates.length + "]");
306306
}
307307
signingCertificate = (X509Certificate) certificates[0];
308308
signingKey = readSigningKey(key, password, terminal);
@@ -329,7 +329,7 @@ private static char[] getChars(String password) {
329329
}
330330

331331
private static PrivateKey readSigningKey(Path path, char[] password, Terminal terminal)
332-
throws Exception {
332+
throws Exception {
333333
AtomicReference<char[]> passwordReference = new AtomicReference<>(password);
334334
try {
335335
return PemUtils.readPrivateKey(path, () -> {
@@ -447,18 +447,18 @@ private RealmConfig findRealm(Terminal terminal, OptionSet options, Environment
447447
}
448448
} else {
449449
final List<Map.Entry<RealmConfig.RealmIdentifier, Settings>> saml = realms.entrySet().stream()
450-
.filter(entry -> isSamlRealm(entry.getKey()))
451-
.collect(Collectors.toList());
450+
.filter(entry -> isSamlRealm(entry.getKey()))
451+
.collect(Collectors.toList());
452452
if (saml.isEmpty()) {
453453
throw new UserException(ExitCodes.CONFIG, "There is no SAML realm configured in " + env.configFile());
454454
}
455455
if (saml.size() > 1) {
456456
terminal.errorPrintln("Using configuration in " + env.configFile());
457457
terminal.errorPrintln("Found multiple SAML realms: "
458-
+ saml.stream().map(Map.Entry::getKey).map(Object::toString).collect(Collectors.joining(", ")));
458+
+ saml.stream().map(Map.Entry::getKey).map(Object::toString).collect(Collectors.joining(", ")));
459459
terminal.errorPrintln("Use the -" + optionName(realmSpec) + " option to specify an explicit realm");
460460
throw new UserException(ExitCodes.CONFIG,
461-
"Found multiple SAML realms, please specify one with '-" + optionName(realmSpec) + "'");
461+
"Found multiple SAML realms, please specify one with '-" + optionName(realmSpec) + "'");
462462
}
463463
final Map.Entry<RealmConfig.RealmIdentifier, Settings> entry = saml.get(0);
464464
terminal.println("Building metadata for SAML realm " + entry.getKey());

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ public static SamlRealm create(RealmConfig config, SSLService sslService, Resour
208208
return realm;
209209
}
210210

211+
public SpConfiguration getServiceProvider() {
212+
return serviceProvider;
213+
}
214+
211215
// For testing
212216
SamlRealm(
213217
RealmConfig config,

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlSpMetadataBuilder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public SamlSpMetadataBuilder(Locale locale, String entityId) {
100100
* @param samlRealm SamlRealm for which SP Metadata is built
101101
*/
102102
public SamlSpMetadataBuilder(SamlRealm samlRealm) {
103-
final SpConfiguration spConfig = samlRealm.getLogoutHandler().getSpConfiguration();
103+
final SpConfiguration spConfig = samlRealm.getServiceProvider();
104104
this.locale = Locale.getDefault();
105105
this.entityId = spConfig.getEntityId();
106106
this.attributeNames = null;

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/saml/RestSamlSpMetadataAction.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ public String getName() {
4444

4545
@Override
4646
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
47-
final SamlSpMetadataRequest SamlSpRequest = new SamlSpMetadataRequest(request.param("realm"));
48-
return channel -> client.execute(SamlSpMetadataAction.INSTANCE, SamlSpRequest,
47+
final SamlSpMetadataRequest SamlSpMetadataRequest = new SamlSpMetadataRequest(request.param("realm"));
48+
return channel -> client.execute(SamlSpMetadataAction.INSTANCE, SamlSpMetadataRequest,
4949
new RestBuilderListener<SamlSpMetadataResponse>(channel) {
5050
@Override
5151
public RestResponse buildResponse(SamlSpMetadataResponse response, XContentBuilder builder) throws Exception {

0 commit comments

Comments
 (0)