General Shibboleth Configuration
Spring Framework
In the old days every application programmer had to define a configuration syntax and write code to parse that syntax and create the objects that perform operations. Earlier releases of Shibboleth worked this way. Shibboleth 3 (and CAS for that matter) are Spring Framework applications. Spring provides a general purpose configuration file system that can be used by any application. XML files define "beans", which are object instances of classes provided by the application. These objects are parameterized by values from the XML file, and they are linked together by plugging a reference to one bean into a property of another bean. For more information, go to the Spring Framework website.
However, Shibboleth 2 already had a configuration syntax where XML files were parsed to create objects, and SAML has standardized XML configuration files that have nothing to do with Spring. So Shibboleth 3 is a hybrid system where some configuration is done with Spring beans and some use SAML or Shib 2 syntax. You can tell the difference by looking at the first XML element. If it is a <beans> element, then this is a Spring bean configuration file in the universal Spring syntax.
In general, each Spring created object is defined by a <bean> element inside the <beans> container. Each <bean> is given the name of the Java class that creates the right type of object, and it has a unique id= by which this bean can be located or referenced by other code. Spring contains some utility classes that make common generic objects easier to define with a specialized element. For example, a Java List object can be created with the Spring <util:list> element which is translated internally to a bean.
Each bean is configured with parameters that can be provided by adding p:parametername="value" elements to the <bean> element or by adding property or constructor elements to the content of the <bean> element.
Through Spring, the Shibboleth application is assembled by selecting optional components, configuring them with parameters, and plugging them into the application framework. For example, Yale used CAS for its SSO, and Shibboleth has a framework for configuring and adding beans that perform authentication. So we use the Unicon supplied integration between CAS and Shibboleth distributed and documented at https://github.com/Unicon/shib-cas-authn3. Remember, Shibboleth was now written to expect that Unicon would add a new component, but because it is configured in Spring as long as Shibboleth provides an Interface on which new classes can be defined, then anyone can write code and plug it into the framework.
Local Disk File Configuration
Even before it used Spring, Shibboleth allowed the non-Spring XML files to come from local hard disk, or from a network URL, or from a Subversion Source Control system. If Shibboleth were a normal application then dynamically obtaining configuration from a Source Control system would be very attractive. Unfortunately, in order to recover from major datacenter failures (as happened when the power was cut to half the servers on campus) Yale stores some of its recovery plans and checklists on cloud services. To login to cloud providers, you need Shibboleth. So Shib is a "Tier 0" application that has to come up before any database, Web, or Source Control servers. That means that everything Shib needs to come up has to be on local hard disk.
So we create a slightly more complicated system. Ultimately, every Shibboleth configuration file is managed by Source Control. Specifically, these files are checked into the yale-shibboleth?-installer project in Source Control (where ? may be replaced with a version reference). However, instead of Shibboleth linking to Source Control at startup or polling Source Control while it is running, a Jenkins Install job is run by operations to check out the current version of all the files from Source Control and then update the configuration of Shibboleth on a running (or temporarily stopped) Shibboleth server. Shibboleth itself is configured to use files on local disk, but Jenkins controls when these files are replaced.
Metadata
The files in the "conf" directory are defined by Shibboleth and are supplied by Yale. One file is metadata-providers.xml. It contains a list of elements that define files or URLs that supply the metadata information defining the applications that support Shibboleth login. While Yale has been forced to create a few metadata files, normally they are supplied by the application vendor.
Each metadata provider element in the metadata-providers file points to a file name in the "metadata" subdirectory of Shib. Optionally it can also point to a URL that Shib can check at configured intervals to look for updates. Yale only uses the URL update facility for the curated InCommon federation aggregated metadata, and we put that metadata source near the end of the list. Because Shibboleth scans all the metadata provider elements in the order they are defined, and it stops when it finds metadata for the entity name it is looking for, Yale configures all the individual metadata files we store on local disk to come first in the search and then configures InCommon. That way if we have a specific metadata file for a partner that is also defined in InCommon, the file we created and store on local disk will be found first and our parameters will be used to talk to that partner.
A metadata provider file can define one metadata for one entity, or it can contain as many entities as you want. Yale could have combined all its local disk metadata into one file with one metadata provider element in the metadata-providers file. That seems simpler, but there is a problem. If an XML file has a syntax error, then the entire file is ignored. So if we combine all our metadata in one big file, then a single missing "/" makes the entire file unreadable. It seems safer even if it makes the configuration file more complicated to separate each metadata configuration for each partner in a separate file, so mistakes are localized to just the one partner with the problem.
We define a number of metadata providers that initially point to empty files on disk. We take advantage of Shibboleth's ability to reload local disk files when they change, and the convention that metadata is taken from the first file that defines a particular entityid. We use this to address a problem with the unreasonably change-adverse IT administration at Yale.
The "emergency-override" dynamic file comes first in the search. Metadata placed in this file with an EntityID that matches an existing Metadata entry in a later file will logically replace the previous version of production Metadata for any partner. When we have a regularly scheduled formal Release of new Shibboleth configuration (on alternate Thursdays) this file is empty. During the two week period, or when it is too later to schedule a regular update through the CAB committee, a runtype=emergency Jenkins Install of Shibboleth modifies just this one file. So if one partner has a problem (typically because a key/certificate changed and we did not know about it in advance) we can go to the Emergency CAB and get approval to put the updated metadata in the emergency-override file, change just that one file on the disk of the running Shib, and fix the problem with that one metadata file. In the next alternate-Thursday full release the changed metadata will be in its normal file and this file will be empty again.
The "additions" dynamic file comes last in the search. Every existing Metadata file will have already been searched, and all existing EntityID values will have matched, so you do not get to this file unless you have a new EntityID that doesn't match any existing one (including all the InCommon entities). This file can only define new Metadata for new entities. This becomes a relatively safe Standard Change that doesn't ahve to be approved because anything put into this file cannot adversely affect existing configured services. Of course, a new partner may also need attributes released to them. Fortunately, Shibboleth allows the function of the attribute-filter.xml file to be broken up into multiple files. Existing partners are configured in attribute-filter, and an empty file named "additional-attribute-filter.xml" is deployed with every Shibboleth Release. Therefore, if a new partner has to be defined to production and cannot wait for the every-other-Thursday Release cycle, the Metadata for that partner can be placed in the metadata/additions.xml file and the attributes to be released can be put in the additional-attribute-filter.xml file. A Jenkins install of runtype=additions replaces both of these originally empty files with the data for the newly defined partner while guaranteeing by their search order that they cannot interfere with existing services. When the next regularly scheduled Shibboleth Release is ready, the changes move from the additions files to the normal Shibboleth configuration and the additions files are empty again.
Two of our partners (Salesforce and Cvent) regularly add new AssertionConsumerService URL elements to their existing Metadata file. This happens so frequently that we have the option of replacing these specific production Metadata files with updated copies. There has not yet been any urgency to make such changes outside a normal Release cycle, but we have the ability to respond to the special needs of these two cloud partners if "every other week" becomes an unacceptable delay.
Jenkins Runtype
The runtype parameter in the Jenkins Install job determines the specific processing that this run of the Install job will perform.
Runtype "install" stops the JBoss server, loads a complete Shibboleth system including potentially new code, and new configuration files.
Runtype "config" does not stop JBoss or the running Shibboleth server. Instead, it replaces the full set of configuration files. The running Shibboleth process checks the timestamps on these files, and when it sees they have changed it loads a complete new configuration. Shibboleth completely reconfigures itself.
Runtype "update" compares the contents of the deployed running Shibboleth configuration to the contents of each configuration file from Source Control and the installer project. If the files are the same, nothing happens. If they are different, then the new contents from Source Control replaces the file on local disk on the Shibboleth server. Since only changed files are reloaded by Shibboleth, this can change only a Metadata file, or only the Attribute Resolver configuration, or only the attribute-filter.
Runtype "additions" modifies only the "additions.xml" Metadata file and the "additional-attribute-filter" file. This can be used to add new Service Providers to production between the every-other-Thursday full Release cycles. Shibboleth isolates these files and appears to guarantee that this type of configuration cannot possibly interfere with existing production services.
Runtype "emergency" will change the "emergency-override.xml" file and allows us to define new Metadata for an existing production partner without affecting anything else. It may require permission from the ECAB, but it is a less dangerous change than a full configuration update. Note that the old Metadata for the partner remains in place, but is not used because the override Metadata is found first in the search order. Before the next Release cycle (the next runtype=install or runtype=config), the old production Metadata should be replaced with the new override data and the emergency-override.xml should be emptied.
Runtype "salesforce" and "cvent" are proposed runtypes that change a single Metadata file for the two partners that require frequent updates.
Contents of the Primary Configuration Files
Attribute-Resolver
Normally Shibboleth has a single attribute-resolver.xml file that contains two types of elements. DataConnectors define database or LDAP queries that produce result sets with columns or LDAP User objects with properties. AttributeDefinitions then take the columns and properties returned by the queries, assign a unique identifier that can be referenced in the attribute-filter (release policy), and supply SAML syntax. So two DataConnectors could query the Yale IDR database for basic identity information, and also the Active Directory for the subset of identity information it contains. Then AttributeDefinition statements can take the "FirstName" column from IDR or the "givenName" property from AD and create various SAML Attributes all with the same value of "Howard" but with SAML name and friendlyName attributes that refer to it as "FirstName", "First Name", "givenName", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", or "urn:oid:2.5.4.42" (informal and formal standards based SAML names for the same thing).
Shibboleth 3 allows the attribute-resolver to be configured with more than one file if you add additional elements to a list in conf/services.xml. Yale uses this to separate all the DataConnector elements which have one syntax and one natural default xml namespace from the AttributeDefinition elements that have a different syntax and a different natural default namespace.
Shibboleth documentation is not particularly clear on the algorithm, so I will try to fill in something that I believe is important to understand.
DataConnectors
There are generally three types of queries that make sense:
- A database query can return exactly one row. Then you can think of the row as a user object, and the column names become the properties of the object.
- A database query can return more than one row but only one column. Then you have a "multivalued" property for one user.
- An LDAP query returns the User object from the directory. LDAP User objects have properties some of which are single valued and some of which are multivalued.
Each column in the result set or property in the LDAP object becomes a Shibboleth IdPAttribute object, but this object has no SAML formatting information and no global name. To find one of these objects, you have to specify the ID of the DataConnector that ran the query and then the column or property name. You need to create one or more AttributeDefinition elements to convert this into a real Shibboleth Attribute with a real ID that you can release as SAML to a partner.
The DataConnector needs a Java DataSource to provide a pool of database connections. Java DataSource management is complicated because it has to know when a database connection must be discarded because it has timed out or because the database rebooted since it was last used. Shibboleth would prefer to leave this to the database experts. Shibboleth 2 did this by using "container managed" connections provided by Tomcat or JBoss. Shibboleth 3 can still use connections managed by the application server, but now that it is a full Spring Framework application it can use DataSources provided and managed by Spring. Either way the complex database management doesn't have to be done by Shib provided code.
AttributeDefinitions
The DataConnector provides the value. We know that "Howard" is the value of the "FirstName" column of the result set returned by the "IDRQuery" database connector or of the "givenName" property of the user object returned by the "ADQuery" LDAP connector.
However, to use this value in any real Shibboleth logic, it needs a single unique "id=" name. That ID can be referenced in the attribute-filter to release the attribute to an application, or it can be used in a Dependency statement to create a variable in a JavaScript block of code, or to create a variable that can be inserted into the WHEN clause of another Database query.
All you need is an AttributeDefinition statement that has a Dependency element that references the ID of the query (IDRQuery or ADQuery in the previous examples) and which specifies the column name or attribute property name as the value of sourceAttributeID=.
<resolver:AttributeDefinition id="idrFirstName" xsi:type="ad:Simple" sourceAttributeID="FirstName"> <resolver:Dependency ref="IDRQuery" /> </resolver:AttributeDefinition>
This AttributeDefinition gives a single unique ID "idrFirstName" to the value of the "FirstName" column returned by the "IDRQuery" DataConnector.
With this AttributeDefinition, you can reference "idrFirstName" in other elements. They can create a variable named "idrFirstName" in a block of JavaScript code, or they could create "$idrFirstName" which can be added to the template for another database query. It would be a really bad idea, but you could create a NameId definition that uses this value as input to a hash that generates the value of the Subject of a SAML Response (except that Subject has to be unique and lots of people have the same FirstName, but this would make sense if the attribute was the Yale UPI number).
About the only thing this AttributeDefinition cannot do as shown is create a SAML Attribute that can be released to a partner and sent in a SAML Response. To do that you need to add one additional element, a SAML Encoder that specifies a friendlyName= like "FirstName" or "GivenName" and an (unfriendly) name= like "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" or "urn:oid:2.5.4.42".
<resolver:AttributeDefinition id="idrFirstName" xsi:type="ad:Simple" sourceAttributeID="FirstName"> <resolver:Dependency ref="IDRQuery" /> <AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:2.5.4.42" friendlyName="givenName" /> </resolver:AttributeDefinition>
Now "idrFirstName" can be released by the attribute-file.xml file and can be sent in a SAML 2 Response. Of course, now the "idrFirstName" field is bound to the LDAP name and friendlyName conventions, which some partners expect. Other partners want another value. If you are going to do this, then you might change the id from "idrFirstName" to "idrFirstNameWithLDAPSyntax".
So an alternative is to create a pure AttributeDefinition using the first example code (without the AttributeEncoder), and then derive additional attributes with Encoders and more specific IDs based on the original:
<resolver:AttributeDefinition id="idrFirstNameWithLDAPSyntax" xsi:type="ad:Simple" sourceAttributeID="idrFirstName"> <resolver:Dependency ref="idrFirstName" /> <AttributeEncoder xsi:type="enc:SAML2String" name="urn:oid:2.5.4.42" friendlyName="givenName" /> </resolver:AttributeDefinition>
This form of AttributeDefinition references a previously defined Attribute (by setting both sourceAttributeID and the Dependency ref to the ID of a previous AttributeDefinition) and then gives it a AttributeEncoder format and a new more specific ID to use in the attribute-filter so you release to a particular partner a value and name+friendlyName format.
XML and Shibboleth are case sensitive, so it is important to realize that Oracle always converts its columns to UPPERCASE. To avoid errors you should always use UPPERCASE names for the sourceAttributeID field if the query is to an Oracle database, and you should define an UPPERCASE id for a default static value in the fallback connector if the Oracle query fails. Otherwise you may spend hours trying debugging the failure of the value to show up where you expect it to be.
Undefined, Null, or Empty
When you query a database or an LDAP directory and then try to define an Attribute based on the value of a column or property, several things can go wrong:
- The database query can return no rows from the query.
- The database query can return a SQL NULL value for a column (unless you use NVL in Oracle or ISNULL in SQL Server to replace the NULL with a default value).
- An LDAP query can return no User object
- An LDAP query can return a User object, but in that object the property you are looking for may not be present.
- An LDAP query can return a User object, and the property may be present, but it may have no values in the list of values.
First the good news. If all you do is to create an AttributeDefinition with an Encoder and to release it as an ordinary Attribute in a SAML Response, then you don't have to think about it. Shibboleth takes care of all these cases and does the right thing.
However, if you want to use this attribute in the SAML NameID server to generate a Subject, then you have a problem because the value that goes into any Subject calculation can never be NULL or missing. So it is your job to make sure that none of these things can happen (nor can duplicate values be returned for different identities) when this attribute is input to a custom element in the saml-nameid.xml file
Then you can run into problems when this attribute is referenced as a Dependency in an AttributeDefinition of type Script. Each Dependency creates a JavaScript variable that can be used in the block of JavaScript code that calculates a value for the new Attribute. However, this variable reflects the peculiar status of the column or property from which it was derived.
A JavaScript variable name can be "undefined" if it has never been used and assigned a value.
A JavaScript variable can be null if it has been assigned the value null.
A JavaScript variable can be an array of length 0 or a collection that contains no objects.
You can encounter all three conditions in a JavaScript variable created by a Dependency element in a type "Script" Attribute definition when different results are returned from a database or LDAP query. The only safe thing to do is to check all three in that specific order:
if (typeof googleEmail!="undefined" && googleEmail!=null && googleEmail.getValues().size()>0) { googlemailalias = googleEmail.getValues().get(0); }
Suppose the Database is Down
If any exception is thrown during the query, then the Shibboleth code will attempt to execute a secondary query specified in the "failover" attribute of the DataConnector. The failover can point to a different query to a different database that might return the same value. Or it can be a Static element.
A Static DataConnector defines one or more property names and values. It is not necessary to define a default value for every property that you could have obtained from the correct execution of the real query, provided that a null or undefined value is acceptable for the other properties.Given the previous warning about NULL and undefined and empty, you should think twice before leaving column/property names without an explicit default value (0, 1, -1, "", "undefined", etc.). However, it is not an error to omit them if you choose. The Static DataConnector cannot throw an exception.
At Yale:
Every Query must have a Failover DataConnector, which may itself have a Failover, and the chain must end with a Static Connector.
See the examples in the attribute-resolver-connectors.xml file.
In vanilla Shibboleth, the only errors that are caught during Attribute evaluation are query errors. Any other error (during JavaScript evaluation, or because of Shibboleth bugs) is fatal and prevents the user from logging on to any partner. Yale has added code to wrap other evaluations with a try-catch that discards attributes in error but preserves all the other attributes. Because attribute errors tend to occur only in new attributes not widely in use or old abandoned attributes no longer in use, this makes Shibboleth more robust against real world errors without impacting security. We will try to interest the Shibboleth maintainers in making this fix standard.
One column/property returned by a query may be used with many different AttributeDefinition statements, each with a different id name and each with a different Encoder configuration. This generates different SAML statements in which the same value is given different names to different partners who expect those names. Some partners like the old LDAP names that are actually a string of numbers. Some like newer Oasis standard names that look like a URL. Some partners simply make up a name and expect us to provide it. The value of firstname in the database column could be used to generate SAML that labels it as "FirstName", "firstName", "first_name", "givenName", "Given Name", and slightly more sophisticated partners will ask for one of three globally unique technical identifiers ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname", "http://schemas.xmlsoap.org/claims/FirstName", and "urn:oid:2.5.4.42"). In addition to different technical standards, there is also confusion because in the Far East the family name comes first and the individual name comes last.
It is also possible to define several Attributes with the same SAML Encoder name string, but with different values. For example, the simple attribute "mail" can refer to the Yale primary Email alias or it can refer to the native mailbox name of the primary account, and I suppose for some partners in some cases it might refer to a non-Yale preferred mailbox. Each AttributeDefinition will have a unique id string, and you should be careful not to release to any party two attributes with the same SAML name but different values because that will just confuse them.
Attribute-Filter
The attribute-filter.xml file has a long list of rules listing the Attributes (defined in the previous section) that are released to each partner. For example
<afp:AttributeFilterPolicy id="releaseToCommunityForceStaging"> <afp:PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="https://yalestaging.communityforce.com" /> <afp:AttributeRule attributeID="givenName"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> <afp:AttributeRule attributeID="sn"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> <afp:AttributeRule attributeID="mail"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> </afp:AttributeFilterPolicy>
CommunityForce gets the givenName (firstname), sn (surname or family name), and E-Mail address (named just "mail" according to the old LDAP standards). In fact, these are all standard old LDAP attributes which are very popular in academic applications. In contrast
<afp:AttributeFilterPolicy id="releaseToArcher"> <afp:PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="https://sso2.archer.rsa.com/adfs/services/trust" /> <afp:AttributeRule attributeID="scopedNetidAsUPN"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> <afp:AttributeRule attributeID="firstnameADFS"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> <afp:AttributeRule attributeID="lastnameADFS"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule> <afp:AttributeRule attributeID="emailADFS"><afp:PermitValueRule xsi:type="basic:ANY" /></afp:AttributeRule>
Archer gets "http://schemas.xmlsoap.org/claims/FirstName" and so on for lastname and email. These are Microsoft URL style names that are more popular these days with everyone except for the old guard in universities who still remember the LDAP names from previous failed attempts to use them.
The Attribute-Filter entries are cumulative. Shibboleth runs through the rules and whenever a rule applies to a entity, any released attributes are added to the list of values we will send. Although most of the time all of the attributes for one entity will be defined in one place, this is a good and sane practice but not a requirement.
Therefore, Shibboleth allows the Attribute-Filter function to be broken up into more than one file. We take advantage of this by creating an attribute-filter.xml file that contains the attributes released to each partner as of an official Shibboleth Release, but then an addtional-attribute-filter.xml file exists initially empty that can be changed between formal releases. The addtional file can either create a new filter policy for a new partner, or it could add an additional attribute to an existing partner.
However, you can only release attributes defined by attribute-resolver.xml, and that does not change between releases.
Relying-Party and Metadata
Metadata is a SAML standard format for describing the Identity Provider (Shibboleth at Yale) and the Service Provider (example: coursera.org). Shibboleth needs Service Provider Metadata for its partners. Although the Metadata file can be quite large and complex, the important information is the EntityID, a unique identifier for the partner, which is typically either a DNS name (coursera.org) or a URL (https://coursera.org). There is also an "AssertionConsumerService URL" that defines the URL to which Shibboleth sends the SAML message that describes the user.
In Shibboleth 2, the relying-party.xml file defines the Metadata sources. In Shibboleth 3, there are separate configuration files for Relying Parties and Metadata Providers.
Each Metadata Provider obtains an XML text file either from disk or from the network. Each XML file contains Metadata for a single entity or for a list of entities.
When Shibboleth needs Metadata information for a specific EntityID, it goes to the first defined Metadata source and looks for that ID. If it finds a match, Shibboleth stops looking. Otherwise it checks the second source and on until it runs out of configured Metadata sources. This "stop at the first match" means that when more than one Metadata Provider has information about an Entity, Shibboleth will use the data from the first configured Provider.
Some partners are configured through a Federation. InCommon, for example, distributes Metadata for a large number of Universities and companies that do business with universities. Periodically Shibboleth obtains updated Metadata from the URL "http://md.incommon.org/InCommon/InCommon-metadata.xml".
Our most important partners exchange Metadata with us directly. We store their Metadata files in a directory in Subversion, and we add a reference to the file name to the relying-party.xml file so Shibboleth will read it. Because we control these local static Metadata files, we put them first before the InCommon dynamic file we do not control.
Shibboleth has a failFastInitialization="false" parameter for each configured Metadata source. The default is "true" and causes Shibboleth to fail to start up if the Metadata is invalid. If we put Metadata directly into production, "true" would be a really, really bad idea. However, at Yale Metadata goes through DEV and TEST before it goes to PROD, and the way the Jenkins jobs interact with the Subversion tags should prevent problems only showing up in production. If we have an issue, it is better that it show up as an initialization problem for DEV and get fixed immediately rather than being something that could just slip through the cracks. Perhaps this parameter should be "true" in DEV and TEST and "false" in PROD, and that will be a change to be made in some later release.
Yale defines four types of Metadata Providers in the following order:
- The dynamic "emergency-override.xml" that is initially empty but can be used to replace production that becomes bad between releases.
- The static production partner Metadata XML files provided for archer, hewitt, communityforce, salesforce, and so on.
- The InCommon remote source which changes without our knowledge or control.
- The dynamic "additions.xml" file where new partners can be defined between releases (also associated with the additional-attribute-filter.xml file).
This then leaves us with a small number of special cases. Two of our partners (salesforce and cvent) use a technique that we might call the Expanding Metadata File. Every time you define a new application with these systems, instead of getting a new Metadata file you get a one line change to add to the existing Metadata file. In Salesforce, the file looks like:
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://yale-finance.my.salesforce.com?so=00Di0000000gP9D" index="12"/> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://yale-fbo.my.salesforce.com?so=00Di0000000gP9D" index="13"/> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://yale-adm.my.salesforce.com?so=00DA0000000ABT0" index="14"/>
The next time someone comes up with a new Salesforce application, it will be index="15" and will have its own unique Location value.
We may add special types of Jenkins Installs (runtype=salesforce and runtype=cvent) that replace just this one file. The bad news is that if the new Metadata is bad it will break existing Salesforce or Cvent applications, but the type of edit here is fairly simple and any mistakes should show up in DEV and TEST. Futhermore, the Shibboleth isloation of Metadata sources and the decision to configure files separately in relying-party.xml ensure that changes to Salesforce only affects Salesforce applications and nothing else.