Related Posts:
Our Virtuous Spiral
OLS.Switch Hardware and Software Recommendations
How Do You Measure Uptime in a Payment Switch?
Here’s an interesting real-world activity that has kept us occupied and stimulated over the past couple of months: an exercise we call the ‘PCI Split’…an acquirer-side exercise we’re undertaking at the behest of one of our clients. This endeavor is far more than an academic exercise. It’s a key component in our client’s PCI DSS compliance strategy. It’s a ‘reduction in scope’ tactic. As our client’s chief strategist and architect explained to us: some of these newer, non-traditional [i.e., vs. core, traditional payment system] applications have a whole different class of users; they want visibility and access to transactional data, but we’re not about to open up the PCI Zone to them.
As a payment systems vendor, I can tell you that once you prove the flexibility and extensibility of your application base, interesting things happen. Indeed, this is one side-effect of the Virtuous Spiral I discussed recently: this transactional creativity that we’ve unleashed through our experience has mashed non-traditional payment applications with payment applications. Thus, the PCI Split! But…how to do it?
The essential first step is to categorize all applications in the environment as PCI or non-PCI. I’ve done that at left. That took a lot more work than you think. Some categorizations are easy: Debit? PCI. EBT? PCI. Loyalty? Non-PCI.
But Electronic Check Acceptance? Upon review: it’s got bank account data, so: PCI. Verizon? That was an interesting debate. Ostensibly, these aren’t payment cards – the account number isn’t a PAN. We had it categorized for a couple of drafts as Non-PCI. Eventually, we settled on PCI. Why? Because cleaving the Stored Value set (there are four SV-class card authorizers in play here; Verizon is one of those) into pieces was proving to be problematic. So, we erred on the side of caution and kept Verizon in the PCI zone.
Next, how to implement the category split. Take a look at this diagram of split mechanics that I scribbled out this morning. [A better version of this diagram is in the works, but this hand-crafted placeholder ought to get the point across for now.] The first thing that ought to jump out at you is that there are three interconnected actors here: the store systems (‘25,000 points of sale’ – that’s the approximate size of this implementation); the Load Balancers; and the OLS Applications (note our replicated application server strategy).
You see how straightforward things are pre-split:
- All Store Systems-originated transactions sent to port 25000 at a virtual IP address
- The Load Balancer forwards all the transactions to OLS applications listening on port 25000 (the Load Balancer has an algorithm to divvy up the flow between APP01 and APP02).
- OLS maintains replicated application servers that maintain a full complement of outbound connections to all remote authorizers
Now, post-split, two things should immediately stand out: how much more refined the model becomes; and the importance of the Load Balancer (and our client’s network architect) in promulgating the strategy. Now, instead of simply throwing all transactions to port 25000, it’s incumbent on the store systems team to divide the traffic by application. [See port slate 25000 – 25012 at top-right of my diagram.]
Then, the brilliance of the network architect’s simple but elegant solution leaps out: our applications still only have to listen on port 25000. Because what the load balancer does is to provide front- and back-end services. It takes in traffic from the slate of 12 entry points and directs the flow to one of two egress points. So, post-split, the load balancers provides two functions: PCI Split directing (new); and application server load balancing (legacy). Wonderful stuff. [Note: the non-PCI servers in the diagram are named ‘NOPAPP0x’ -- ‘NOP’ = Non=PCI]
Of course, this is not without work for us. We had to come up with a strategy that allows us to maintain, configure and build PCI and non-PCI instances of our application. There’s a list of reasons why we need to take it to that level. I’ll give you one example: we don’t want AMEX and FDR components deployed on the Non-PCI servers. Yes, it’s true that because these aren’t in the PCI zone we’ll never connect (and the load balancer will never provide us relevant traffic to take advantage of those links). But from a sheer aesthetic standpoint, it’s terrible: we’ll have orphaned components that’ll show up as persistently red lights on our Status screen.
We had a goal, however, that we would not fork our application to accomplish these objectives. Pulling off this project with a one-time exercise is one thing; but having to manage two source code bases long-term would be a mess. And, if a fork was in play, we’d basically need to declare a moratorium on any application changes while the split project was happening. With the unleashed creativity I’ve mentioned here, it’s tough to put the genie back in the bottle and say “no changes for X period.” That doesn’t work. We wanted to keep a single modularized code base - and handle environment-specific deployments via configuration techniques. This is how we handled it:
We focused on the build process – we (and by ‘we’, I mean ‘Dave’) crafted an iterative series of changes to the ‘ant’ build process that end up producing – upon request – either a PCI- or NOP-specific release. For example, if the requestor desires a PCI version of the release, then we do these actions during the ‘ant’ build:
<target name="configure-pci" description="Removes non-pci configuration and deploy files.">
<delete file="${build.deploy}/10_authreturn_channel.xml"/>
<delete file="${build.deploy}/10_loyaltyengine_channel.xml"/>
<delete file="${build.deploy}/10_methcheck_channel.xml"/>
<delete file="${build.deploy}/10_rest_txnmgr.xml"/>
<delete file="${build.deploy}/20_authreturn_mux.xml"/>
<delete file="${build.deploy}/20_loyaltyengine_mux.xml"/>
<delete file="${build.deploy}/20_methcheck_mux.xml"/>
<delete file="${build.deploy}/30_authreturn_txnmgr.xml"/>
<delete file="${build.deploy}/30_loyalty_txnmgr.xml"/>
<delete file="${build.deploy}/30_methcheck_txnmgr.xml"/>
<delete file="${build.deploy}/30_nop_main_txnmgr.xml"/>
<move file="${build.deploy}/30_pci_main_txnmgr.xml" tofile="${build.deploy}/30_main_txnmgr.xml"/>
<delete file="${build.deploy}/50_authreturn_unhandled.xml"/>
<delete file="${build.deploy}/50_loyaltyengine_unhandled.xml"/>
<delete file="${build.deploy}/50_methcheck_unhandled.xml"/>
<delete file="${build.deploy}/90_nop_jetty.xml"/>
<move file="${build.deploy}/90_pci_jetty.xml" tofile="${build.deploy}/90_jetty.xml"/>
<delete file="${build.deploy}/market_basket_response.inc"/>
</target>
I won’t reproduce the NOP version here, except to say (1) it does the same type of actions mechanically; and (2) the list of items we remove is a lot longer.
You see two types of actions here:
- Obvious: Where we simply delete a component that is only relevant on the other environment. For example, Methcheck is a non-PCI application, so we remove all related components in the PCI build request.
- Less Obvious: Where we need to maintain two separate versions of the same file in our repository, but only deliver one similarly-named target to both environments. Best example – take a look at what we’ve done with the ‘main’ transaction manager. That’s what I call our ‘dispatcher’: the ‘pci’ version of it only recognizes transactions from the PCI slate of applications. If (god forbid) we start getting non-PCI transactions on the PCI servers, we want to treat those as ‘UNKNOWN’ requests. That’s what we’ve implemented here with this little nuance. […clearly, the mirror strategy is in place on the non-PCI build and deployment.]
Recent Comments