From 08dfa2ef6779bc4aac2b21d43fb581a2cecb0364 Mon Sep 17 00:00:00 2001 From: Jason McIntosh Date: Tue, 13 May 2025 17:01:21 -0500 Subject: [PATCH 1/2] fix(saml): With the upgrade to spring saml off of the DSL signing saml requests were broken. This restores that capability --- .../gate/security/saml/SAMLConfiguration.java | 7 + .../security/saml/SecuritySamlProperties.java | 150 +++++++++++++++++- .../saml/SecuritySamlPropertiesTest.java | 36 +++++ gate-saml/src/test/resources/certificate.pem | 19 +++ gate-saml/src/test/resources/private_key.pem | 28 ++++ gate-saml/src/test/resources/saml_signing.p12 | Bin 0 -> 2547 bytes 6 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java create mode 100644 gate-saml/src/test/resources/certificate.pem create mode 100644 gate-saml/src/test/resources/private_key.pem create mode 100644 gate-saml/src/test/resources/saml_signing.p12 diff --git a/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SAMLConfiguration.java b/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SAMLConfiguration.java index 7f3f1493a8..32e5afa7aa 100644 --- a/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SAMLConfiguration.java +++ b/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SAMLConfiguration.java @@ -87,6 +87,13 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { if (decryptionCredential != null) { builder.decryptionX509Credentials(credentials -> credentials.add(decryptionCredential)); } + // This is used in some identity providers to sign the request. NOT the response - response + // is handled via the certs in metadata or up above. This is keycloak and some others + // TO USE THIS: The certificate should be uploaded to the IDP to allow it to decrypt these + // requests + if (properties.isSignRequests()) { + builder.signingX509Credentials(c -> c.addAll(properties.getSigningCredentials())); + } RelyingPartyRegistration registration = builder.build(); return new InMemoryRelyingPartyRegistrationRepository(registration); } diff --git a/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlProperties.java b/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlProperties.java index 288234b097..258a0cb081 100644 --- a/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlProperties.java +++ b/gate-saml/src/main/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlProperties.java @@ -20,23 +20,33 @@ import com.netflix.spinnaker.kork.annotations.NullableByDefault; import com.netflix.spinnaker.kork.exceptions.ConfigurationException; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.PrivateKey; +import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; import java.util.Enumeration; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.validation.constraints.NotEmpty; +import lombok.Data; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; @@ -47,11 +57,37 @@ @ConfigurationProperties("saml") @NullableByDefault public class SecuritySamlProperties { + public static final String FILE_PREFIX = "file:"; private Path keyStore; private String keyStoreType = "PKCS12"; private String keyStorePassword; private String keyStoreAliasName = "mykey"; // default alias for keytool + // the privatekey/cert location files can be generated via + // openssl req -new -x509 -nodes -keyout private_key.pem -out certificate.pem -subj + // "/CN=Spinnaker" -days 3650 + @Data + @NoArgsConstructor + public static class Credential { + private String privateKeyLocation; + private String certificateLocation; + + public Credential(String privateKeyLocation, String certificateLocation) { + setCertificateLocation(certificateLocation); + setPrivateKeyLocation(privateKeyLocation); + } + + public void setCertificateLocation(String certificateLocation) { + this.certificateLocation = addFilePrefixIfNeeded(certificateLocation); + } + + public void setPrivateKeyLocation(String privateKeyLocation) { + this.privateKeyLocation = addFilePrefixIfNeeded(privateKeyLocation); + } + } + + private List signingCredentials = new LinkedList<>(); + public Saml2X509Credential getDecryptionCredential() throws IOException, GeneralSecurityException { if (keyStore == null) { @@ -71,6 +107,82 @@ public Saml2X509Credential getDecryptionCredential() return Saml2X509Credential.decryption(privateKey, certificate); } + private static String addFilePrefixIfNeeded(String property) { + if (StringUtils.hasLength(property) && property.startsWith("/")) { + return FILE_PREFIX + property; + } + return property; + } + // @formatter:off + /** + * Try to match the standard spring saml config LIKE the below. This would be a keylcoak + * configuration example. Note that this does NOT match, but at least the private key suff does. * + *
+ * + * + *
+   * spring:
+   *   security:
+   *     saml2:
+   *     ## We ARE a relying party.
+   *       relyingparty:
+   *         registration:
+   *           SSO:
+   *           ## This would be the SSO provider information.  Asserting party would be who's sending the SAML payload (e.g. okta/keycloak/etc)
+   *             assertingparty:
+   *               metadata-uri: http://192.168.1.2:8080/realms/master/protocol/saml/descriptor
+   *               entityId: Spinnaker
+   *               ## This uses the signing credentials in a separate block.  Why it's there vs. here is strange
+   *               singlesignon:
+   *                 sign-request: true
+   *                 url: http://192.168.1.2:8080/realms/master/protocol/saml
+   *               ## This IF metadata-uri is set SHOULD NOT be needed as it's PARSED as part of the operation
+   *               ## this is the decryptionCredentials
+   *               verification:
+   *               ## Nominally this could be in a JKS or a P12 keystore as well.  REMINDER:  this is basically
+   *               ## coming from the metadata in NORMAL cases.
+   *                 credentials:
+   *                 - certificate-location: encryptedFile:k8s!n:saml-verification-secret!k:certificate.pem
+   *                   private-key-location: encryptedFile:k8s!n:saml-verification-secret!k:private_key.pem
+   *             signing:
+   *               credentials:
+   *               - certificate-location: encryptedFile:k8s!n:saml-signing-secret!k:certificate.pem
+   *                 private-key-location: encryptedFile:k8s!n:saml-signing-secret!k:private_key.pem
+   *
+   * 
+ * + * * + * + * NOTE: We ONLY support a single credential with this config. + * + * @return the credentials piece based upon privateKey/certificate + */ + // @formatter:on + // TODO: Replace this entire thing with standard spring security saml loading vs. doing all this + // ourselves + // See: + // https://docs.spring.io/spring-boot/api/java/org/springframework/boot/autoconfigure/security/saml2/Saml2RelyingPartyProperties.Registration.html for the auto wiring based approach + @Nonnull + public List getSigningCredentials() { + if (this.signingCredentials != null && !signingCredentials.isEmpty()) { + return this.signingCredentials.stream() + .map( + each -> + getSaml2Credential( + each.getPrivateKeyLocation(), + each.getCertificateLocation(), + Saml2X509Credential.Saml2X509CredentialType.SIGNING)) + .toList(); + } + return new LinkedList<>(); + } + + /** + * Sign requests via the WantAuthnRequestsSigned XML flag. Defaults to false. Keycloak defaults to + * true, okta DOES NOT support true. Uses the signing credentials + */ + private boolean signRequests = false; + /** URL pointing to the SAML metadata to use. */ private String metadataUrl; @@ -112,9 +224,7 @@ public String getAssertionConsumerServiceLocation() { @PostConstruct public void validate() throws IOException, GeneralSecurityException { - if (StringUtils.hasLength(metadataUrl) && metadataUrl.startsWith("/")) { - metadataUrl = "file:" + metadataUrl; - } + metadataUrl = addFilePrefixIfNeeded(metadataUrl); if (keyStore != null) { if (keyStoreType == null) { keyStoreType = "PKCS12"; @@ -159,4 +269,38 @@ public static class UserAttributeMapping { private String username; private String email; } + + /// Filched DIRECTLY from spring-security private temporarily until all of this is replaced + // directly with the autowired spring configuration stuff + /// + // https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java + private static final ResourceLoader resourceLoader = new DefaultResourceLoader(); + + private static Saml2X509Credential getSaml2Credential( + String privateKeyLocation, + String certificateLocation, + Saml2X509Credential.Saml2X509CredentialType credentialType) { + RSAPrivateKey privateKey = readPrivateKey(privateKeyLocation); + X509Certificate certificate = readCertificate(certificateLocation); + return new Saml2X509Credential(privateKey, certificate, credentialType); + } + + private static RSAPrivateKey readPrivateKey(String privateKeyLocation) { + Resource privateKey = resourceLoader.getResource(privateKeyLocation); + try (InputStream inputStream = privateKey.getInputStream()) { + return RsaKeyConverters.pkcs8().convert(inputStream); + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + + private static X509Certificate readCertificate(String certificateLocation) { + Resource certificate = resourceLoader.getResource(certificateLocation); + try (InputStream inputStream = certificate.getInputStream()) { + return (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(inputStream); + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } } diff --git a/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java b/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java new file mode 100644 index 0000000000..91f56ace6e --- /dev/null +++ b/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java @@ -0,0 +1,36 @@ +package com.netflix.spinnaker.gate.security.saml; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.security.saml2.core.Saml2X509Credential; + +class SecuritySamlPropertiesTest { + + @Test + public void verifyCanLoadCerts() { + SecuritySamlProperties properties = new SecuritySamlProperties(); + properties.setSigningCredentials( + List.of( + new SecuritySamlProperties.Credential( + "classpath:private_key.pem", "classpath:certificate.pem"))); + List signingCredentials = properties.getSigningCredentials(); + assertThat(signingCredentials.get(0).getPrivateKey().getAlgorithm()).isEqualTo("RSA"); + } + + @Test + public void verifyCanLoadCertsFromAFileLocation() { + SecuritySamlProperties properties = new SecuritySamlProperties(); + Path currentDir = Paths.get(""); + properties.setSigningCredentials( + List.of( + new SecuritySamlProperties.Credential( + currentDir.toAbsolutePath() + "/src/test/resources/private_key.pem", + currentDir.toAbsolutePath() + "/src/test/resources/certificate.pem"))); + List signingCredentials = properties.getSigningCredentials(); + assertThat(signingCredentials.get(0).getPrivateKey().getAlgorithm()).isEqualTo("RSA"); + } +} diff --git a/gate-saml/src/test/resources/certificate.pem b/gate-saml/src/test/resources/certificate.pem new file mode 100644 index 0000000000..ef6bbd6135 --- /dev/null +++ b/gate-saml/src/test/resources/certificate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUOevToAcJr3FsI/MsoEq75EwMt5QwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJU3Bpbm5ha2VyMB4XDTI1MDUwOTIwNTE0OFoXDTM1MDUw +NzIwNTE0OFowFDESMBAGA1UEAwwJU3Bpbm5ha2VyMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAryEo1o+YyVssgFuI5N5U3eQyvAoGbtXCeaCoR1LltAqq +SrDGyrzJnB3kXtd0RieGK0AxBg+FgL/Xl8kw+p+72h7gW8XYi9gS92fGYVyoY9nz +c9WueQpazzfuQjx5UuDqZn59f6EsAEAwzIAep9YHwysAuma8PYlNzqnhTWTu5tJG +SvCD5kv11njYHJJ1ntIaUHGNlpHH2rAPWKcf8XeqBfvFgBHDLRpuj+2XOijwY6uz +iOgnsViOG/NbC7zBbpkwELoWcxsh3msLs4HIs6DRULtQq6EUnvjx72pwM14GlQ+9 +ZrcnxtX7qQ4ODf2rNpobmoBkE2mfV72vNS4J5XpltwIDAQABo1MwUTAdBgNVHQ4E +FgQU9I5Nl3n/D1gdrOapVfpRUTUSfPwwHwYDVR0jBBgwFoAU9I5Nl3n/D1gdrOap +VfpRUTUSfPwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfW58 +p9z8T+OUbi284Eejvy+k4hDvCB3+90GoiXrVQvhBlfXr9eO4rzkuOQe9Qw4jVf47 +RBwvUuen+gOMo6zx+PK7wKTZqmluKzPf5ic1XOl7M9wy14g84bBWP5EniJdqzpup +WQK58sYWZc0QrpwGbWIkr7QQGju0F4nte8at/MvK9uHmtK5j6Rg3axPjLInIAuXq +d/+H+CKRbybwS/ZCp37rjD9pyVYYIQ7ZZt/0EZCAaAKPeZ6RFhIguIpx1Ww5mMrF +JK1LnWPuzr8R30SoH/BRQwU/sM7cvYsmhVsKNPfaaEbakqfo+LtSD+yrtRvbCo69 +c1ZqBjsV5B1736V+CQ== +-----END CERTIFICATE----- diff --git a/gate-saml/src/test/resources/private_key.pem b/gate-saml/src/test/resources/private_key.pem new file mode 100644 index 0000000000..e217deb661 --- /dev/null +++ b/gate-saml/src/test/resources/private_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCvISjWj5jJWyyA +W4jk3lTd5DK8CgZu1cJ5oKhHUuW0CqpKsMbKvMmcHeRe13RGJ4YrQDEGD4WAv9eX +yTD6n7vaHuBbxdiL2BL3Z8ZhXKhj2fNz1a55ClrPN+5CPHlS4Opmfn1/oSwAQDDM +gB6n1gfDKwC6Zrw9iU3OqeFNZO7m0kZK8IPmS/XWeNgcknWe0hpQcY2WkcfasA9Y +px/xd6oF+8WAEcMtGm6P7Zc6KPBjq7OI6CexWI4b81sLvMFumTAQuhZzGyHeawuz +gcizoNFQu1CroRSe+PHvanAzXgaVD71mtyfG1fupDg4N/as2mhuagGQTaZ9Xva81 +LgnlemW3AgMBAAECggEAAOs3ZIFN+b6p13ZMxTXacJWmyK9CVCfDA5pYqbyNgfdf +15sfDUyrCf0xYqpBnnZIwKmpMwW/TwPHTCS7BB5gS0EWaPZmelm8ilF/KRz2h+op +fz4+f6ynXLvLteyK+ibCEPkUVoMuQEXT4OBWsA+0NqhGfN0pccvqdTOZnOlFt/0A +yv2GfYLlP13nreU+u4efE7/i+E8xvUxdwH4sLSuKMIB/0aYI6LbdwJbqh5KMBLVt +u+ooXRk8atsK7OucpSuo5Wj5jzUJV31PfYtpcnVnatLMjPAA+LQAYDrZkHLMXvv8 +opuyHExZlJ5iWx6tViQz0B1hrWcCIHS7cvx5+D/a6QKBgQDb9Crz3AOeZH3IR6Ha +Zws4rhlqtq5Co8DAyTHE62XWkdTY4hhtfhvQ+8YH9jzU+fIBS39QVlM73MmPR4ga +GtSTtsRNN1Oukouq+QvsXx9qR0XEoRBofVmo5VQilosZmqlAjc1vGCyDHpszCUOG +pMVP/2lEiknheSCZobK9XfhQlQKBgQDL1HUPKQaR2iBHPnri7tZOD2K4c8jrwwVX +YjGuUxyzyrnQq8XPkNolatPXxRoqIyqDDfXCafIC8g/BkAY9TxLhoyQEnN6aRODV +ds3wKy/Dn09YaH4BtPbXaNv5cz6JkHtgtl6z6EXXaInb5GATGaHEw9KpH4/eWwIH +A92iZyrOGwKBgQCaleSKNxsj+ySb2hxazwkH8PRUF8gpdcVGuSCNcZPFVgDt3Rml ++ne6TPlFJz5hwLjhSBpWcBVXgTj3xiJVln3Iwy77xeK+UqhupVJH8iK2IxlZtIk/ +prmZBnQ3Su7AM/64K/EyHx9Jl/0jxWL8Alnae3uUfEyoduT+lLJ2fNDEcQKBgAnc +vsk7//BgsH0h/corKj1eqzUnjQozRnfi7Wp05QeiAHmjRg/z/0oeMB/ZjpmJWA49 +R63feHFCCxcfg93FjLFUNnLusCqguIw7kl1TiZ0agTlS3P3yJptnnHUmaVk4n2+f +g1eLHo38peb41tk1vUkK/I9oUoq8to1mV3v7J+wPAoGBALbIYJ4O8FfclXR+63TK +36FYlcHL2u0JjGXyByLM8x6xv9eHeFfij66k4OxWC9vpr3fO/AkbGWnceiztjca5 +lejCMjQkU7qbNRAr+XUuk9d6qD+sHd1XLYeXNY9WW9Ki8xbms9C7q/C/nq0uWyHu +KyrlMN7DaB1PfNSRFDPBIEgv +-----END PRIVATE KEY----- diff --git a/gate-saml/src/test/resources/saml_signing.p12 b/gate-saml/src/test/resources/saml_signing.p12 new file mode 100644 index 0000000000000000000000000000000000000000..5cd42d467777701ffdddc6f9dcfd31c3cf78e6f2 GIT binary patch literal 2547 zcmai$X*AS}8^>p7tV4*g-!P1p--I%ZjIys|iI9DlE&CE>?E4aOE!n#EEF)QGipUzW zrO;I|NV0_7EM52f|EK%%zIe`azTfYQ=bY!9=W`x_jNE{Lp#T~A2Lg$W)r|el3T6No zlaV;>AsJU&z ztowiwwN1Tc$R82)E|Yc7Pa>|;sdQxUO%K$L4?{}I4E$J*UoZ7R*PVT|d24Ishn{Uqa;iBS+VuuZ+keI)TV`O9>o}YxG=e;PH~7*>gy!k2*w7cRAx*=1vcQ>aUqK1wW58J1^_&Rbe{MB~xT?5Lo#p965| zyq8p~Rz$cH=ov}i&NY=E7gi3wi_P%`YFthpKH_+9?1f=y9sF!4@N)0T`QRBV%G2|E zuUk=G`CJ`dOYJK&{x3ePyow5K)2!2n>5o2E>*pYeO?lZFW~|zuVJhbrjz4U5NJQRy z?~sT7%FncK?vSTWn9;l5SQKn|_>7awY%T1=w7iLDUXj;%5?V#RJ0@m<^&=xtg>w_F}K;-NpKA>r0)! zk;?w|yK%8RU+l*2wwMcZxvTe)N`ZQ;jJ2wBrnq;N3t8Msl?5K8dX@EdB2)NdN5s4u z^SonC=M~chhL$Z>fKfv{i?wsu#VlMoR`B>~YB~ z?nqj*9t8EgqKx)CE;j7)n)k7nWIn)(Zx7C=#5diNo&~t*MFXG6tx39%S|gL3E3`VV z9#o1CXO*?p-<6YPdD8415;BHwaj}N`*Ah({&@G+VuVe?BVmG(@Z?0}QHjbQ#iGgz3wkdqL9V#@s&2-u?=oNMRhHoDff8ai|` zXa5rfeIW{2u`U%H_E(lL8>4_aj0_ut=TwjCxN>2LJlMsQ*N8$*qC-Qvo(*mHkI97_ zJmWxM%9^!Ym(q^Xs|a=!;vJiUj!H#ta2d(JMoL%L@HY%65wttx^ds$ETRlyfY~f+C z9qJY_@n@hSr#JGT!{y9SgKgOJqOqlKpZcep1mfo3Jgw`b(>$Z&m6IwpMhDQxy1O0!(5vWh1=ab=(sMWx#7D`^B36=Q~j&RT4SjKd?+>f;QM{n zE81pK0?R!D2zW-+-VHegwq9%6AQ0uWn?%p-xRp$*1qpp0fb2gRBABm z9}gyX<;&jz#Ons0O<{-Ar!UC{ZDqaZEVp~sWqPzQ}F&SI83BPBnd zaABDyvpXW5u`#~7P^D>)8J}LP4|w76Npk;Mx5KzWn>|isxQbU$%kSJ1=9sOTC_+1w zAkZ~?D{eN}(ulW;eI%7o@lGjoXS#6iT2ScFD^w&8FT3?`_IchaN_0%xdp6#t zZYSpF)rRvpX_p$JtF7mKNK8|8S3Gko&(^6)wxnk@eFc5iQ#{%Z@hzuXIX%jBTe;^* z#XA}IxczaNOcAj?$!Zs882Ix^3=QRbrur?V3Gm|y9Oc11g*2o_62H0njlyw>@Sr%N z-!8r^l4AAyro)imS~jKuh}-SuXQ1#hor20K&)%u(Via13E>0ddC79+)* z^ig+yDvR`pdQo~>TEx7%XVrf4B~dAknr_0m$q&_Yb<2%0%)J3qPA1NmzDrdf9z<-J zQ<~133m5Az$;b!|hREmQ!ztPdf*YfI0~!#2H7k(#U$Gw?^1Y1+;Dg$FKB)XgxitF&o*Rpr=mhb*`fWSG zWq=~OFw7=uhv@5Wx-V3H12J_SlLPnrwAu~!BL)rP)ewD&17)N4_>zGueDZt z&D?}kX`)71T%=9R{V}J?(!<%KhlonQglHdC>DDUcjdId3;?Y2!FwNDK6aIvmv!+zo%k&%a*Goh5PdzRocKRhTw%8x7s>*ZD&$fKqn zZ0z4t2CiVNe6Z?td+E}!+pEf{@*{}F=-onJ*DhJ{5Q+9q*WtC(3I|II9MdN017vf5 zwN)|}Ck~wX1zIFGjt9nt(tG4y0kuqGkQGWE(YRZSKjjb!r~o*C_1BvO0mDHsk*V#D uarMaJtU*~7f&Th|J1T|B=AEML>iYCF7#(OlfH7)ceLU5+UbFrWYX1gc{D&9- literal 0 HcmV?d00001 From c6d3791054598ed5aecdf960d1b9d2587b8f7fc7 Mon Sep 17 00:00:00 2001 From: Jason McIntosh Date: Wed, 14 May 2025 11:19:33 -0500 Subject: [PATCH 2/2] fix(license): Fix missing license entry --- .../saml/SecuritySamlPropertiesTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java b/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java index 91f56ace6e..58722fd68c 100644 --- a/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java +++ b/gate-saml/src/test/java/com/netflix/spinnaker/gate/security/saml/SecuritySamlPropertiesTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2025 Harness, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package com.netflix.spinnaker.gate.security.saml; import static org.assertj.core.api.Assertions.assertThat;