1 | ## SAML delegation
|
2 |
|
3 | Some advanced SAML use cases involve a single logical transaction that spans one or more intermediate
|
4 | clients or servers. A common example includes a SAML-enabled web site acting on behalf of a logged-in
|
5 | user while accessing additional SAML-enabled services, which are not directly accessible
|
6 | by the user agent, e.g. a database. Generalizing this example, a number of intermediaries might be
|
7 | traversed before the final point of access. Popular ways of making this happen are
|
8 | __SAML impersonation__, __SAML assertion forwarding__ and __SAML IdP proxy__.
|
9 |
|
10 | All of them suck:
|
11 |
|
12 | * SAML impersonation forces principal to disclose credentials to an SP.
|
13 | * SAML forwarding makes IdP lose track of who and when is using forwarded assertions.
|
14 | * IdP proxy requires a second IdP in exotic configuration.
|
15 |
|
16 | When a SAML assertion is used as a security token to authenticate/authorize people and software
|
17 | against a mission-critical service, it is important that the identities and order of intermediaries,
|
18 | if any, are expressed within the token in some fashion.
|
19 |
|
20 | __SAML assertion delegation__ moves beyond the forwarding scenario by adding information to the
|
21 | assertion that explicitly identifies the chain of parties through which a transaction flows.
|
22 | Delegation assures that all requests in the chain are routed back through the identity provider at
|
23 | each hop to cryptographically guarantee that each party has been authenticated and appropriate
|
24 | policy enforced. This finegrained and real-time enforcement capability is a key advantage over
|
25 | pure SAML forwarding and impersonation, at a price of additional back-channel operation for
|
26 | each delegation hop.
|
27 |
|
28 | [__Shibboleth Identity Provider__]() implements SAML 2.0 profile for assertion delegation (Liberty/IDWSF).
|
29 |
|
30 | __hana-saml-wsse__ is the corresponding client. It also implements an __ECP__ client, which is handy
|
31 | for testing your delegation configuration, but can be very useful in its own right, e.g.
|
32 | if you are implementing impersonation or fowarding model.
|
33 |
|
34 | The package name is historical; the client has been originally implemented for a specific use case
|
35 | that involved a SAP HANA database backend. Making Shibboleth delegation work with SAP HANA requires
|
36 | additional IdP configuration, which is not covered here.
|
37 |
|
38 | ## Compatibility
|
39 |
|
40 | This implementation is known to work with Shibboleth IdP 3.2. There is some indication that ECP
|
41 | and ID-WSF profiles are supported by some other open source and commercial IdP
|
42 | systems, but none of them were tested.
|
43 |
|
44 | ## X.509 authentication
|
45 |
|
46 | As it should be clear from the example below, ID-WSF requires that your IdP is configured to support
|
47 | __X.509 client certificate authentication__. The same applies to all TLS middleware you might have
|
48 | in front of your IdP, e.g. nginx, httpd, netscaler, etc. This is at least as complicated as it
|
49 | sounds, so good luck. Good starting points are:
|
50 |
|
51 | * [Tomcat SSLValve](https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/SSLValve.html)
|
52 | * [X.509 Client-Side Certificate Auth with Nginx](http://nategood.com/client-side-certificate-authentication-in-ngi)
|
53 |
|
54 | > Things get easier if you want to use this library in ECP mode. Although ECP *does* support X.509 auth,
|
55 | > it is optional (you can use basic HTTP auth method).
|
56 |
|
57 | ## Security
|
58 |
|
59 | - The client signs all outgoing AuthRequests.
|
60 | - The client expects incoming assertions to be both encrypted and signed.
|
61 | - `inResponseTo` validation is supported and enabled by default.
|
62 | - `NotBeforeOrAfter` is not validated, but is returned in `assertionInfo` object.
|
63 | - `wsa` profile does not work over plain HTTP, and never will.
|
64 | - `ecp` mode _should_ work over plain HTTP, but this is a very bad idea.
|
65 |
|
66 | ## Install
|
67 |
|
68 | ```
|
69 | $ npm install hana-saml-wsse
|
70 | ```
|
71 |
|
72 | ## IdP configuration
|
73 |
|
74 | The following configuration snippet assumes the following scenario:
|
75 |
|
76 | - A Service Provider idendified as `webserver-sp` is allowed to obtain a
|
77 | delegatable assertion using ECP at any time. Assertion is valid for 10 hours.
|
78 | - By itself, a delegatable assertion in not usable by any other SP other than
|
79 | `webserver-sp` as its `Audience` restriction is limited to `webserver-sp`.
|
80 | - However, for as long as delegatable assertion remains valid, `webserver-sp` can
|
81 | request additional, "delegated" assertions which will be valid for `database-sp`,
|
82 | each valid for 60 seconds. These delegated assertions are no further delegatable,
|
83 | i.e. `database-sp` is the final point of access.
|
84 |
|
85 | ___
|
86 |
|
87 | > Note that, while requesting a delegated assertion, `webserver-sp` does not need
|
88 | > to present actual credentials of the user (generally, username and password), and it
|
89 | > is not expected to have them - instead, it presents a delegatable assertion as the
|
90 | > means of confirming that it has the authority to impersonate that user on `database-sp`
|
91 | > or elsewhere. The communication takes place over authenticated TLS channel, i.e.
|
92 | > IdP uses TLS to ensure it is actually talking to the SP to which the delegatable
|
93 | > assertion was originally issued. __In essence, this is the whole idea of how
|
94 | > assertion delegation works__.
|
95 | ___
|
96 |
|
97 | ```xml
|
98 | <!-- conf/relying-party.xml -->
|
99 |
|
100 | <!--
|
101 | "delegation-predicate" is a reference to a bean that implements
|
102 | com.google.common.base.Predicate<ProfileRequestContext<?,?>> interface.
|
103 | It can be used to implement additional custom logic to selectively allow
|
104 | or disallow delegation. In the most basic case, you want to implement a predicate that
|
105 | always returns true.
|
106 | -->
|
107 |
|
108 | <bean parent="RelyingPartyByName" c:relyingPartyIds="#{{ 'webserver-sp' }}">
|
109 | <property name="profileConfigurations">
|
110 | <list>
|
111 | <!-- serve long-lived delegatable assertions via ECP -->
|
112 | <bean id="SAML2.ECP"
|
113 | class="net.shibboleth.idp.saml.saml2.profile.config.ECPProfileConfiguration"
|
114 | p:inboundInterceptorFlows="security-policy/saml2-ecp"
|
115 | p:allowDelegation-ref="delegation-predicate"
|
116 | p:assertionLifetime="PT600M"
|
117 | />
|
118 | <!-- serve short-lived delegated assertions via ID-WSF -->
|
119 | <bean id="Liberty.SSOS"
|
120 | class="net.shibboleth.idp.saml.idwsf.profile.config.SSOSProfileConfiguration"
|
121 | p:inboundInterceptorFlows="security-policy/saml2-idwsf-ssos"
|
122 | p:maximumTokenDelegationChainLength="1"
|
123 | p:allowDelegation="false"
|
124 | p:delegationPredicate-ref="delegation-predicate"
|
125 | p:additionalAudiencesForAssertion="#{{ 'database-sp' }}"
|
126 | p:assertionLifetime="PT1M"
|
127 | />
|
128 | </list>
|
129 | </property>
|
130 | </bean>
|
131 | ```
|
132 |
|
133 | ## SP configuration
|
134 |
|
135 | SP metadata must inform IdP that SP is ready and willing to consume delegatable assertions,
|
136 | as shown below. The `Location` attribute of `AssertionConsumerService` must match `consumerURL` parameter
|
137 | of `hana-saml-wsse` client configuration, but this is a formality as SP is not expected
|
138 | to actually respond on that URL.
|
139 |
|
140 | ```xml
|
141 | ...
|
142 | <!-- SSOS -->
|
143 | <AssertionConsumerService
|
144 | Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
|
145 | Location="https://my.org/Liberty/SSOS"/>
|
146 |
|
147 | <AttributeConsumingService>
|
148 | <RequestedAttribute
|
149 | NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
150 | Name="urn:liberty:ssos:2006-08"
|
151 | FriendlyName="assertionDelegation"
|
152 | isRequired="false" />
|
153 | </AttributeConsumingService>
|
154 | ...
|
155 | ```
|
156 |
|
157 | ## Usage
|
158 |
|
159 | The example below matches the IdP configuration above and:
|
160 |
|
161 | 1. Binds to your Identity Provider's ECP endpoint using TLS transport.
|
162 | 2. Authenticates using either user/password or X.509 certificate of the user.
|
163 | 3. Retrieves, verifies and decrypts a delegatable assertion.
|
164 | 4. Creates an ID-WSF AuthRequest using delegatable assertion as a security token.
|
165 | 5. Binds to the ID-WSF endpoint using TLS transport.
|
166 | 6. Authenticates using X.509 certificate of the SP.
|
167 | 7. Retrieves, verifies and decrypts a delegate assertion.
|
168 | 8. Tests it by using it as a security token to connect to a SAP HANA backend.
|
169 | 9. Optionally repeats steps 4-7 every `deleg_test_repeat_ms` milliseconds.
|
170 | ___
|
171 |
|
172 | ```javascript
|
173 | // test.js
|
174 | var
|
175 | hdb = require('hdb'),
|
176 | precise = require('precise'),
|
177 | moment = require('moment'),
|
178 | xmlfmt = require('xmlfmt'),
|
179 | WSS = require('hana-saml-wsse')
|
180 | ;
|
181 |
|
182 | var
|
183 | deleg_test_repeat_ms = 3000,
|
184 | idpHost = 'idphost',
|
185 | idpPort = 443,
|
186 | http_ua = 'hana-wss-client/1.0',
|
187 | hdb_conf = {
|
188 | host: 'hanahost',
|
189 | port: 30015
|
190 | }
|
191 | ;
|
192 |
|
193 | var conf = {
|
194 | user : 'tj_holowaychuk',
|
195 | pass : null, // leave blank for TLS CCA (@see user_key/user_cert)
|
196 |
|
197 | idpHost : idpHost,
|
198 | idpPort : idpPort,
|
199 |
|
200 | idpId : 'idp-id',
|
201 | spId : 'webserver-sp',
|
202 |
|
203 | url: {
|
204 | ecp : '/idp/profile/SAML2/SOAP/ECP',
|
205 | wsa : '/idp/profile/IDWSF/SSOS'
|
206 | },
|
207 |
|
208 | consumerURL : 'https://my.org/Liberty/SSOS',
|
209 |
|
210 | idp_cert : './idp.pem',
|
211 |
|
212 | // used both for signing and X.509 auth (WSA only)
|
213 | sp_key : './sp_private_key.pem',
|
214 | sp_cert : './sp_public_key.pem',
|
215 |
|
216 | // optional TLS CCA authentication (ECP only)
|
217 | user_key : './user.key',
|
218 | user_cert : './user.crt',
|
219 |
|
220 | sig_alg : 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
221 |
|
222 | tls_opt: {
|
223 | hostname : idpHost,
|
224 | port : idpPort,
|
225 | method : 'POST',
|
226 | headers : {
|
227 | 'Content-Type' : 'text/xml',
|
228 | 'Transfer-Encoding' : 'chunked',
|
229 | 'User-Agent' : http_ua
|
230 | },
|
231 | // die on bad IdP cert
|
232 | rejectUnauthorized: true
|
233 | },
|
234 |
|
235 | log: console.log
|
236 | };
|
237 |
|
238 | var client = new WSS.client(conf);
|
239 |
|
240 | function hdbtest(assertion, cb) {
|
241 | var hdbclient = hdb.createClient(hdb_conf);
|
242 | hdbclient.connect({ assertion: assertion }, (err) => {
|
243 | if (err) {
|
244 | console.error('[hdb] error:', err);
|
245 | } else {
|
246 | console.log('[hdb] i am', hdbclient.get('user'));
|
247 | }
|
248 | hdbclient.end();
|
249 | cb && cb(err);
|
250 | });
|
251 | }
|
252 |
|
253 | client.get('ecp', (err, delegatableAssertion, assertionInfo) => {
|
254 | if (err)
|
255 | return conf.log('[ecp] error:', err);
|
256 |
|
257 | conf.log('[ecp] rcvd delegatable assertion, expires',
|
258 | moment(assertionInfo.notOnOrAfter).fromNow());
|
259 |
|
260 | //conf.log(xmlfmt(delegatableAssertion))
|
261 |
|
262 | var delegator = () => {
|
263 | var timer = precise().start();
|
264 | client.get('wsa', (err, assertion, assertionInfo) => {
|
265 | if (err)
|
266 | return conf.log('[wsa] error:', err);
|
267 |
|
268 | //conf.log(xmlfmt(assertion));
|
269 | //conf.log(assertion);
|
270 |
|
271 | conf.log('[wsa] rcvd delegated assertion in',
|
272 | (timer.stop().diff()/1000000000).toFixed(2) + 's,',
|
273 | 'expires', moment(assertionInfo.notOnOrAfter).fromNow());
|
274 |
|
275 | hdbtest(assertion, () => {
|
276 | if(!deleg_test_repeat_ms)
|
277 | process.exit(0);
|
278 | });
|
279 | }, delegatableAssertion);
|
280 | };
|
281 |
|
282 | if (deleg_test_repeat_ms)
|
283 | setInterval(delegator, deleg_test_repeat_ms);
|
284 | else
|
285 | delegator();
|
286 | });
|
287 |
|
288 | //:~
|
289 | ```
|
290 |
|
291 | You should get something like:
|
292 |
|
293 | ```
|
294 | $ node test.js
|
295 |
|
296 | [ecp] using tls cca with user key
|
297 | [ecp] rcvd delegatable assertion, expires in 10 hours
|
298 | [wsa] using tls cca with SP key
|
299 | [wsa] rcvd delegated assertion in 0.22s, expires in a minute
|
300 | [hdb] i am tj_holowaychuk
|
301 | [wsa] using tls cca with SP key
|
302 | [wsa] rcvd delegated assertion in 0.14s, expires in a minute
|
303 | [hdb] i am tj_holowaychuk
|
304 | [wsa] using tls cca with SP key
|
305 | [wsa] rcvd delegated assertion in 0.16s, expires in a minute
|
306 | [hdb] i am tj_holowaychuk
|
307 | [wsa] using tls cca with SP key
|
308 | [wsa] rcvd delegated assertion in 0.11s, expires in a minute
|
309 | [hdb] i am tj_holowaychuk
|
310 | [wsa] using tls cca with SP key
|
311 | [wsa] rcvd delegated assertion in 0.14s, expires in a minute
|
312 | ^C
|
313 |
|
314 | ```
|
315 |
|
316 | ## Recommended reading
|
317 |
|
318 | SAML specs are fun:
|
319 |
|
320 | * [Overview of SAML assertion delegation scenarios](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-delegation-cs-01.pdf)
|
321 | * [SAML V2.0 Enhanced Client or Proxy (ECP)](https://wiki.oasis-open.org/security/SAML2EnhancedClientProfile)
|
322 | * [WS-Security SAML Token](http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0.pdf)
|
323 | * [SAML Metadata Specification](https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf)
|
324 | * [IDWSF-SAML Specification](http://www.projectliberty.org/resource_center/specifications/liberty_alliance_id_wsf_2_0_specifications/)
|
325 | * [Understanding WS-Security](https://msdn.microsoft.com/en-us/library/ms977327.aspx)
|
326 | * [WSA SOAP binding](https://www.w3.org/TR/2006/REC-ws-addr-soap-20060509/)
|
327 | * [urn:oasis:names:tc:SAML:2.0:cm:holder-of-key](http://hdknr.github.io/docs/identity/SamlHokProfile.html)
|
328 |
|
329 | ## License
|
330 |
|
331 | MIT. Use at your own risk. As with all things SAML, always be sure you know what you are doing.
|