Handling payment card information (PCI) involves complex regulation and a lot of paperwork.

In this article we'll be looking at:

PCI and those Requirements

When you enter your card details online, you want to make sure that those details are secure. The PCI requirements aim to enhance global payment account data security by developing standards.

There are a group of 12 PCI DDS requirements which you must meet to achieve PCI Compliance for your solution. Each one contains many sub-clauses and completing even the simplest PCI certification requires a lot of work and a really solid architecture.

  1. Protect your system with firewalls
  2. Configure passwords and settings
  3. Protect stored cardholder data
  4. Encrypt transmission of cardholder data across open, public networks
  5. Use and regularly update anti-virus software
  6. Regularly update and patch systems
  7. Restrict access to cardholder data to business need to know
  8. Assign a unique ID to each person with computer access
  9. Restrict physical access to workplace and cardholder data
  10. Implement logging and log management
  11. Conduct vulnerability scans and penetration tests
  12. Documentation and risk assessments

So why use Serverless and AWS?

When building a solution, you want to actually build your solution.

You don't want to be spending all of your time reading PCI regulations, designing process to meet the regulations and then writing it all into compliance documentation.

When you work with a cloud provider, they've done a lot of the work already. AWS have a list of PCI compliant services.

Here is an infographic showing where the responsibilities lie when building a system with EC2s.

As you can see, AWS will handle the physical security and hardware configuration, but the rest it up to you.

If you were to go with Serverless then the story is quite different...

With Serverless services, AWS do a lot for you. They manage resourcing, the runtime environment, container isolation and much more.

This means that all of those compliance requirements also fall under their responsibility.

You still have a good set of requirement that you need to meet on your own, but they're just over half what it could have been.

Design Patterns and Processes to Tackle the Rest

Now that you've chosen to build your solution using Serverless services, you might think that the rest will be easy.

Unfortunately there are some very tricky requirements.

Luckily we have some lovely design patterns and systems that you can put in place to help tackle them and reduce your work.

Then we'll be looking at some specific Services and Tools for your Tech Stack

Multi-Account AWS Setup

When being assessed for PCI compliance, everything that could possibly have access to card data is classed as being in-scope and is subject to the full requirements.

If you can prove that a section of your solution has no possible way to access the card data then is is classed as out-of-scope.

With your whole solution in-scope, everything has to follow the strictest of rules. For example, a developer wouldn't be able to add a new dependency to the project without reviewing it and fixing every possible vulnerability. This slows down the development of your whole solution, not just the parts that handle card data.

Luckily, you can architect your accounts in a way that proving that only parts of your solution are in-scope is relatively easy. We call this de-scoping parts of your system.

Just remember when setting this all up that you should be using a single AWS organization to contain all of your accounts. This makes billing and organisation much easier

To de-scope large sections of your solution you can use a setup like this:

  • Each environment has (at least) two AWS Accounts.
  • One account contains just the resources that will come in contact with card details.
  • One (or more) accounts that only ever reference card details by a reference key.

Architecting  your solution in this way means that your card data will always be secure and compliant but the rest of the app doesn't need to follow such strict rules.

This means your team can use the packages they want, access resources through the console and deliver features more easily.

Using STS Assume-Roles

There will always be situations where resources in your non-PCI environment need to know information stored in the PCI account. This could be the status of a payment, expiry date on the card or a billing address.

To access this information, a Lambda in the non-PCI account could use a STS assume-role to trigger a Lambda in the PCI account. The triggered Lambda could retrieve the data and return it (as long as it doesn't include PCI Data).

There are two requirements for this:

  • This STS assume role will have very limited permissions, allowing it to trigger a single Lambda.
  • The triggered Lambda needs minimal permissions and has to be closely assessed to ensure that there isn't a data leak vulnerability.

As long as you do both of these things, this pattern for data access can be very powerful and keeps the non-PCI account out of scope.

Multiple Environment Accounts

Another trick to do with AWS Accounts is to have a set of accounts per environment.

This sound like a lot of effort for something that could be done just with resource_${environment} but separating by AWS accounts gives you much more.

As with the separate accounts for PCI and non-PCI, resources in different AWS accounts have no access to resources in another account unless explicitly given.

With different environment accounts this means there's no way that some tests you're doing on staging can effect the production service.

Having completely separate environments can also help

Separating your environments also helps reduce human error as if you're working in the development account, you can't accidentally delete a production database.

And user access brings us nicely onto our next topic which is single sign on.

AWS Single Sign On

Now that we have multiple AWS accounts for PCI and non PCI services, and then have duplicated those accounts for each environment we need a way of managing who can do what in which account.

User access is also a requirement of PCI so its two birds with one stone.

AWS provides a service called single sign-on (AWS SSO) which allows you to give your your AWS uses a single place to sign in and it also gives your administrator a single place to manage who can do what in all of your accounts

You manage your user access by creating  groups and permission sets. These can be combined to give really fine grained access to each of your AWS accounts. It also can integrate with your existing active directory so that your users can sign in with their company credentials.

The process is pretty simple:

  1. Set up AWS SSO and then create users and assign them to groups (or connect your existing Active Directory with all of your users and groups)
  2. Create a Permission set that you want a group to be able to use (admin-full-access or developer-read-only) using IAM
  3. In AWS SSO, select an account that you want give access to
  4. Choose the group/s that you want to access this account
  5. Select the Permission set that you want the group/s to be able to use

Repeat steps 3-5 as many times as needed until every group has the right level of access to the correct accounts.

Also remember that you want as few people as possible to have access to the production PCI account. You can even design it in a way that no-one ever needs access to the account.

Here you can see the access for an account. There are two different groups who have can use different permission sets.

This is also a really nice user experience for your team. They log into a single location and then can easily access all of the accounts they need to.

Services and Tools for your Tech Stack

Now we're moving on to the more technical side of things. We'll be looking at some services and tools that can really help make PCI Compliance a lot easier to achieve.

Amazon Cognito CloudWatch and CloudTrail

Two of the remaining requirements are to:

  • Have authorisation controls for anyone accessing applications and data
  • Log and maintain audit trails of all access to applications and data

Amazon Cognito provides a really simple way of  storing users, authenticating them and have controlling the access they have.

This is all built into a really easy-to-use  service will help you meet the first of these requirements.

Cognito also works really well with CloudWatch and CloudTrail which are Solutions that allow you to log and audit access to your applications and data.

With these three services in place you'll be able to to monitor exactly who accesses what data.

These logs can automatically be transported to a central location for monitoring. At this point you can connect your monitoring tools of choice.

Logs also need to be stored for at least one year so they can be stored in S3, and then lifecycle events can be used to move them to S3-Glacier for long term archive storage after 30 days.


Another of the requirements for storing card data, as well as a best practice for all of your customer data, is to have it encrypted.

Encrypting all of your data, managing the keys and rotations can become a large and complex task. Its an ongoing overhead that can become an unwanted burden on your team.

With DynamoDB, encryption comes by default which can save your team a massive amounts of time and effort.

It also has other benefits such as as massive scalability, redundancy and it works beautifully with the next tech on this list. This makes it my go-to choice for database storage in the cloud.


AWS Lambda is the backbone of Serverless development.  It provides you with an incredible amount of computer power whilst not charging you a penny when it's not running.

Not having to think about implementing autoscaling, redundancy, IP addresses and networking can save your developers a chunk of time.  

Each Lambda is also isolated from all of the other Lambdas in your AWS accounts. This makes it much more secure and allows you to to control its permissions with much greater granularity. We'll be talking about that a little bit later.

The Serverless Framework

With all of these services we need a nice way of managing and controlling it all.

For that a great option is the Serverless Framework this is an open source  tool that allows you to easily create solutions using the services have already talked about.

It does this by allowing you to define your infrastructure using code.  Creating your infrastructure this way makes it easier to analyse and more reliable to reproduce.

This also no act as self documentation, explicitly stating what are deploying to which account. This ticks off one more of the the requirements as well as creating a great development experience.

The way that the Serverless Framework works is that it takes your infrastructure definitions and turns them into CloudFormation templates. These are yaml or JSON files that AWS knows how to read. When these are loaded into an AWS account, CloudFormation will deploy whatever has been defined in the template.

You could write all of the CloudFormation templates your self but the Serverless Framework adds a layer of abstraction and really simplifies the process.

The Serverless Framework also has a huge community of users who have built an amazing list of plugins. These can improve your developer workflow, reduce repetitive code, make configuring complex services much simpler and much more.

Some of my favourite plugins include serverless-offline with serverless-dynamodb-local for running your solutions locally, serverless-dynamodb-autoscaling if you use provisioned DynamoDB, and serverless-iam-roles-per-function for making IAM Least privilege much easier.

IAM Least Privilege

Using the serverless framework also enables you to control exactly what permissions each and every bit of code has. You can create a custom IAM policy for each It can do on each of your resources.

This process is called granting least privilege and is yet another of the requirements that is needed for  PCI compliance.

In your function definitions you can define the exact permissions that the Lambdas will have.

When you do this for each of your Lambda you can explicitly show that this Lambda doesn't have access to the 'CardDataTable' and can't invoke another function which does. Having this explicit access will make documenting and meeting the PCI requirements much easier.

Static Code Analysis

As I mentioned earlier the serverless framework generate CloudFormation templates which are then used to create the infrastructure on your AWS accounts. We can add an extra layer of security by analysing these CloudFormation templates to ensure that they comply with the PCI regulations and best practices.

There are lots of of air which can help you with this, allowing you to define the exact rules that you want to test against. One example is CFN-NAG (short for CloudFormation Nag) and can be integrated into your development process as well as your CI/CD processes.

When running CFN_NAG it will be looking for all of these things, ensuring that PCI requirements and best practices are followed. You can also select which rules you want to follow or even add your own custom rules. By default it will look for:

  • IAM rules that are too permissive (wildcards)
  • Security group rules that are too permissive (wildcards)
  • Access logs that aren't enabled
  • Encryption that isn't enabled
  • Password literals

As well as tools for scanning the CloudFormation templates there are also tools that you can use to scan the code that will be running inside your Lambdas. These tools can do things such as checking for passwords,  insuring PCI data is not  your logs.

This static analysis of both the code and the CloudFormation templates is another requirement of PCI  that can be met with these tools.

Runtime Event Analysis

With traditional architecture, your code will largely be triggered through endpoints on your server. When you're running a truly serverless solution there will be a huge range of different services that are triggering a Lambda.

Your Lambda could be triggered by a DynamoDB change, a file uploaded to S3, or it could be directly invoke by another Lambda. In these situations a traditional firewall all or even a web application firewall (WAF) just won't cut it.

With API Gateway you provide request/response data mappings that can help sanitise the range of possible inputs, but this only works for Lambdas behind an API Gateway. We want a solution that will work for all Lambdas.

You need a way of ensuring that the event that is triggering the Lambda isn't malicious, and this can only happen actually within the Lambda.

There are are different ways of doing this each having their own benefits and drawbacks. Here are the two most common.

The first and probably the simplest is to to define the schema of the expected event and validate all events that are received against that schema. this solution is great as it is easy to understand and there are an abundance of services out there that you can use for this.

YUP is a package that is very commonly used in the JavaScript world for validating objects and can be easily added at the start of every Lambda function insuring that the event payload is at least in the right format.

The drawbacks to this method is that there is the possibility of someone maliciously putting content into that event object so that it's still meets the required schema. You could create further tests for this if your security specialist says that it's a requirement.

Another solution is to use a service which provides you with a package that will scan every event for malicious content. This will cover both malformed events and malicious content that is embedded in the correct form.


If you go back to the diagram that we looked at at the start of this article we can review which of the requirements we have met with the Solutions with discussed.

The authentication of users as well as the authorisation  controls for accessing application and data have been met with Cognito and AWS Single Sign-On.

CloudWatch and CloudTrail Help us meet the the log and audit trail requirements

The YUP schema analysis or or paid security solution meet the firewall and event data inspection requirement.

And finally using the Serverless Framework with the plugins and tools we’ve talked about will meet the requirements for least privilege IAM, application behaviour, code scanning and configuration, as well as the requirement to maintain a full cloud asset inventory.

This setup will also provide a solution for gathering and transporting all of your logs. From there you can easily monitor errors and security incidents.  I've chosen not to to discuss specific tools for monitoring as as there are a lot of tools out there are and everyone has their own preferences.

To meet the data leek protection requirement you'll still need to architect your application effectively, but the DynamoDB encryption, IAM least privilege, account isolation and the single sign-on will tick a lot of the boxed already.

This leaves the responsibility of detecting and fixing vulnerabilities in third-party dependencies, and removing obsolete Cloud services. These are tasks that will need to be continuously completed throughout the development cycles of your solution.

If we try and compare are these responsibilities to the responsibilities you would have had if you went with a more traditional architecture the time, effort and resource savings become very apparent.

If you've found this article useful and would like us to help you with your AWS solutions then please drop me an email at Sam@CompleteCoding.io.

We help companies like yours make the most of AWS and Serverless. Whether you're building a small internal application or already have an international SaaS solution we can help.

Sam Williams
Complete Coding Director