mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
saml: introduce saml2.check.signature (#9219)
Adminstrators should ensure that IDP configuration has signing certificate for the actual signature check to be performed. In addition to this, this change introduces a new global setting `saml2.check.signature` which can deliberately fail a SAML login attempt when the SAML response has missing signature. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
8221be3a8c
commit
78ace3a750
@ -144,6 +144,14 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||||||
return responseObject;
|
return responseObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkAndFailOnMissingSAMLSignature(Signature signature) {
|
||||||
|
if (signature == null && SAML2AuthManager.SAMLCheckSignature.value()) {
|
||||||
|
s_logger.error("Failing SAML login due to missing signature in the SAML response and signature check is enforced. " +
|
||||||
|
"Please check and ensure the IDP configuration has signing certificate or relax the saml2.check.signature setting.");
|
||||||
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Signature is missing from the SAML Response. Please contact the Administrator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final InetAddress remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
|
public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final InetAddress remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
|
||||||
try {
|
try {
|
||||||
@ -225,6 +233,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||||||
session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
|
session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
|
||||||
|
|
||||||
Signature sig = processedSAMLResponse.getSignature();
|
Signature sig = processedSAMLResponse.getSignature();
|
||||||
|
checkAndFailOnMissingSAMLSignature(sig);
|
||||||
if (idpMetadata.getSigningCertificate() != null && sig != null) {
|
if (idpMetadata.getSigningCertificate() != null && sig != null) {
|
||||||
BasicX509Credential credential = new BasicX509Credential();
|
BasicX509Credential credential = new BasicX509Credential();
|
||||||
credential.setEntityCertificate(idpMetadata.getSigningCertificate());
|
credential.setEntityCertificate(idpMetadata.getSigningCertificate());
|
||||||
@ -238,9 +247,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||||||
params, responseType));
|
params, responseType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (username == null) {
|
|
||||||
username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
|
username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value());
|
||||||
}
|
|
||||||
|
|
||||||
for (Assertion assertion: processedSAMLResponse.getAssertions()) {
|
for (Assertion assertion: processedSAMLResponse.getAssertions()) {
|
||||||
if (assertion!= null && assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
|
if (assertion!= null && assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
|
||||||
@ -272,6 +280,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Signature encSig = assertion.getSignature();
|
Signature encSig = assertion.getSignature();
|
||||||
|
checkAndFailOnMissingSAMLSignature(encSig);
|
||||||
if (idpMetadata.getSigningCertificate() != null && encSig != null) {
|
if (idpMetadata.getSigningCertificate() != null && encSig != null) {
|
||||||
BasicX509Credential sigCredential = new BasicX509Credential();
|
BasicX509Credential sigCredential = new BasicX509Credential();
|
||||||
sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
|
sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate());
|
||||||
|
|||||||
@ -25,51 +25,54 @@ import java.util.Collection;
|
|||||||
|
|
||||||
public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService {
|
public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService {
|
||||||
|
|
||||||
public static final ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
|
ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
|
||||||
"Indicates whether SAML SSO plugin is enabled or not", true);
|
"Indicates whether SAML SSO plugin is enabled or not", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
|
ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
|
||||||
"SAML2 Service Provider Identifier String", true);
|
"SAML2 Service Provider Identifier String", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
|
ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
|
||||||
"SAML2 Service Provider Contact Person Name", true);
|
"SAML2 Service Provider Contact Person Name", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
|
ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
|
||||||
"SAML2 Service Provider Contact Email Address", true);
|
"SAML2 Service Provider Contact Email Address", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
|
ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
|
||||||
"SAML2 Service Provider Organization Name", true);
|
"SAML2 Service Provider Organization Name", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
|
ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
|
||||||
"SAML2 Service Provider Organization URL", true);
|
"SAML2 Service Provider Organization URL", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso",
|
ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso",
|
||||||
"SAML2 CloudStack Service Provider Single Sign On URL", true);
|
"SAML2 CloudStack Service Provider Single Sign On URL", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/",
|
ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/",
|
||||||
"SAML2 CloudStack Service Provider Single Log Out URL", true);
|
"SAML2 CloudStack Service Provider Single Log Out URL", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
|
ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
|
||||||
"The CloudStack UI url the SSO should redirected to when successful", true);
|
"The CloudStack UI url the SSO should redirected to when successful", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
|
ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
|
||||||
"Attribute name to be looked for in SAML response that will contain the username", true);
|
"Attribute name to be looked for in SAML response that will contain the username", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
|
ConfigKey<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
|
||||||
"SAML2 Identity Provider Metadata XML Url", true);
|
"SAML2 Identity Provider Metadata XML Url", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no",
|
ConfigKey<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no",
|
||||||
"The default IdP entity ID to use only in case of multiple IdPs", true);
|
"The default IdP entity ID to use only in case of multiple IdPs", true);
|
||||||
|
|
||||||
public static final ConfigKey<String> SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1",
|
ConfigKey<String> SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1",
|
||||||
"The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "SHA1,SHA256,SHA384,SHA512");
|
"The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "SHA1,SHA256,SHA384,SHA512");
|
||||||
|
|
||||||
public static final ConfigKey<Boolean> SAMLAppendDomainSuffix = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.append.idpdomain", "false",
|
ConfigKey<Boolean> SAMLAppendDomainSuffix = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.append.idpdomain", "false",
|
||||||
"If enabled, create account/user dialog with SAML SSO enabled will append the IdP domain to the user or account name in the UI dialog", true);
|
"If enabled, create account/user dialog with SAML SSO enabled will append the IdP domain to the user or account name in the UI dialog", true);
|
||||||
|
|
||||||
public static final ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "1800",
|
ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "1800",
|
||||||
"SAML2 IDP Metadata refresh interval in seconds, minimum value is set to 300", true);
|
"SAML2 IDP Metadata refresh interval in seconds, minimum value is set to 300", true);
|
||||||
|
|
||||||
|
ConfigKey<Boolean> SAMLCheckSignature = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.check.signature", "false",
|
||||||
|
"Whether SAML2 signature must be checked, when enforced and when the SAML response does not have a signature would lead to login exception", true);
|
||||||
|
|
||||||
public SAMLProviderMetadata getSPMetadata();
|
public SAMLProviderMetadata getSPMetadata();
|
||||||
public SAMLProviderMetadata getIdPMetadata(String entityId);
|
public SAMLProviderMetadata getIdPMetadata(String entityId);
|
||||||
public Collection<SAMLProviderMetadata> getAllIdPMetadata();
|
public Collection<SAMLProviderMetadata> getAllIdPMetadata();
|
||||||
|
|||||||
@ -535,6 +535,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
|
|||||||
SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
|
SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
|
||||||
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
|
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
|
||||||
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
|
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
|
||||||
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout};
|
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -271,6 +271,30 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
|
|||||||
verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, hasThrownServerApiException, 0, 0);
|
verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, hasThrownServerApiException, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||||
|
Field f = ConfigKey.class.getDeclaredField(name);
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(configKey, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailOnSAMLSignatureCheckWhenFalse() throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
overrideDefaultConfigValue(SAML2AuthManager.SAMLCheckSignature, "_defaultValue", "false");
|
||||||
|
SAML2LoginAPIAuthenticatorCmd cmd = new SAML2LoginAPIAuthenticatorCmd();
|
||||||
|
try {
|
||||||
|
cmd.checkAndFailOnMissingSAMLSignature(null);
|
||||||
|
} catch(Exception e) {
|
||||||
|
Assert.fail("This shouldn't throw any exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ServerApiException.class)
|
||||||
|
public void testFailOnSAMLSignatureCheckWhenTrue() throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
overrideDefaultConfigValue(SAML2AuthManager.SAMLCheckSignature, "_defaultValue", "true");
|
||||||
|
SAML2LoginAPIAuthenticatorCmd cmd = new SAML2LoginAPIAuthenticatorCmd();
|
||||||
|
cmd.checkAndFailOnMissingSAMLSignature(null);
|
||||||
|
}
|
||||||
|
|
||||||
private UserAccountVO configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(String entity, String configurationValue, Boolean isUserAuthorized)
|
private UserAccountVO configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(String entity, String configurationValue, Boolean isUserAuthorized)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Mockito.when(samlAuthManager.isUserAuthorized(nullable(Long.class), nullable(String.class))).thenReturn(isUserAuthorized);
|
Mockito.when(samlAuthManager.isUserAuthorized(nullable(Long.class), nullable(String.class))).thenReturn(isUserAuthorized);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user