© Kasun Indrasiri and Prabath Siriwardena 2018
Kasun Indrasiri and Prabath SiriwardenaMicroservices for the Enterprisehttps://doi.org/10.1007/978-1-4842-3858-5_11

11. Microservices Security Fundamentals

Kasun Indrasiri1  and Prabath Siriwardena1
(1)
San Jose, CA, USA
 

The microservices architecture expands the attack surface with multiple microservices communicating with each other remotely. Instead of having one or two entry points, now we have hundreds of entry points to worry about. It’s a common principle in security that the strength of a given system is only as strong as the strength of its weakest link. The more entry points we have, the broader the attack surface, and the higher the risk of being attacked. Unlike in a monolithic application, the depth and breadth we need to worry about in securing a microservice is much higher. There are multiple perspectives in securing microservices: secure development lifecycle and test automation, security in DevOps, and application level security.

Note

In 2010, it was discovered that since 2006, a gang of robbers equipped with a powerful vacuum cleaner had stolen more than 600,000 euros from the Monoprix supermarket chain in France. The most interesting thing was the way they did it. They found out the weakest link in the system and attacked it. To transfer money directly into the store’s cash coffers, cashiers slid tubes filled with money through pneumatic suction pipes. The robbers realized that it was sufficient to drill a hole in the pipe near the trunk and then connect a vacuum cleaner to capture the money. They didn’t have to deal with the coffer shield.

The key driving force behind the microservices architecture is the speed to production (or the time to market). One should be able to introduce a change to a service, test it, and instantly deploy it into production. A proper secure development lifecycle and test automation strategy needs to be there to make sure that we do not introduce security vulnerabilities at the code level. We need to have a proper plan for static code analysis and dynamic testing — and most importantly those tests should be part of the continuous delivery (CD) process. Any vulnerability should be identified early in the development lifecycle and should have shorter feedback cycles.

There are multiple microservices deployment patterns — but the most commonly used one is service-per-host model. The host does not necessarily mean a physical machine — most probably it would be a container (Docker). The DevOps security needs to worry about container-level security. How do we isolate a container from other containers and what level of isolation we have between the container and the host operating system? Apart from containers, Kubernetes as a container orchestration platform introduced another level of isolation, in the form of a pod. Now we need to worry about securing the communication between containers as well as between pods. In Chapter 8, “Deploying and Running Microservices,” we discussed containers and security in detail. Another important pattern with respect to microservices deployment is the Service Mesh , which we discussed in detail in Chapter 9, “Service Mesh”. In a typical containerized deployment in Kubernetes, the communication between pods always happens through a Service Mesh, to be precise, through the Service Mesh proxy. The Service Mesh proxy is now responsible for applying and enforcing security between two microservices.

How do we authenticate and access control users to microservices and how do we secure the communication channels between microservices? All this falls under application-level security. This chapter covers security fundamentals with a set of patterns to address the challenges we face in securing microservices at the application level. What does it mean to secure a microservice? How does securing a microservice differ from securing any other service? What’s so special about microservices? All these questions will be addressed in this chapter. Security at the development time and in the deployment process (with Docker and Kubernetes) is out of the scope of this book. We encourage the readers who are keen on knowing all the aspects in microservices security to refer to a book specifically focusing on microservices security.

Monolith versus Microservices

In a monolithic application, where all the services are deployed in the same application server, the application server itself provides session management features. The interactions between services happen over local calls and all the services can share the user’s login status. Each service (or the component) needs not to authenticate the user independently. Authentication will be done centrally at an interceptor, which intercepts all the service calls. Once the authentication is completed, it passes the login context of the user between the services (or components) and that process varies from one platform to another. Figure 11-1 shows the interactions between multiple components in a monolithic application. The monolithic application is deployed in a single application container, probably on a bare metal host machine or on a virtual machine.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig1_HTML.jpg
Figure 11-1

Sharing a user session in a monolithic application

In a Java EE environment, the interceptor can be a servlet filter. This servlet filter will intercept all the requests coming to its registered contexts and will enforce authentication. The service invoker should either carry valid credentials or a session token that can be mapped to a user. Once the servlet filter finds the user, it can create a login context and pass it to the downstream components. Each downstream component can identify the user from the login context to do any authorization.

Security becomes challenging in a microservices environment. In the microservices world, the services are scoped and deployed in multiple containers in a distributed setup. The service interactions are no more local, but remote, mostly over HTTP. Figure 11-2 shows the interactions between multiple microservices.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig2_HTML.jpg
Figure 11-2

Interactions between multiple microservices

The challenge here is how we authenticate the user and then pass the login context between microservices in a symmetric manner, and then how does each microservice authenticate to each other and authorize the user. The following section explains the different techniques to secure service-to-service communication in the microservices architecture, both for authentication and authorization, and also to propagate user context across different microservices.

Securing Service-to-Service Communication

Service-to-service communication can happen synchronously via HTTP or asynchronously via event-driven messaging. In Chapter 3, “Inter-Service Communication,” we discussed both synchronous and asynchronous messaging between microservices. There are two common approaches to secure service-to-service communication. One is based on JSON Web Token (JWT) and the other is based on Transport Layer Security (TLS) mutual authentication. In the following section, we look at the role of JWT in securing service-to-service communication in the microservices architecture.

JSON Web Token (JWT)

JWT (JSON Web Token) defines a container to transport data between interested parties (see Figure 11-3). It became an IETF standard in May 2015 with the RFC 75191. A JWT can be used to:
  • Propagate one’s identity information between interested parties. For example, user attributes such as first name, last name, email address, phone number, etc.

  • Propagate one’s entitlements between interested parties. The entitlements define what the user is capable of doing at a target system.

  • Transfer data securely between interested parties over an unsecured channel. A JWT can be used to transfer signed and/or encrypted messages.

  • Assert one’s identity, given that the recipient of the JWT trusts the asserting party (the token issuer). For example, the issuer of a JWT can sign the payload with its private key, which makes it protected for integrity, so that no one in the middle can alter the message. The recipient can validate the signature of the JWT, by verifying it with the corresponding public key of the issuer. If the recipient trusts the public key known to it, it also trusts the issuer of the JWT.

../images/461146_1_En_11_Chapter/461146_1_En_11_Fig3_HTML.jpg
Figure 11-3

Transporting data between interested parties via a JWT

A JWT can be signed or encrypted or both. A signed JWT is known as a JWS2 (JSON Web Signature) and an encrypted JWT is known as a JWE3 (JSON Web Encryption) . In fact a JWT does not exist itself — either it has to be a JWS or a JWE. It’s like an abstract class — the JWS and JWE are the concrete implementations. Let me be little precise here. JWS and JWE have a broader meaning beyond JWT. JWS defines how to serialize (or represent) any signed message payload; it can be JSON, XML, or can be in any format. In the same way, JWE defines how to serialize an encrypted payload. Both JWS and JWE support two types of serializations: compact serialization and JSON serialization. We call a JWS or JWE, a JWT only if it follows the compact serialization. Any JWT must follow compact serialization. In other words a JWS or JWE token, which follows JSON serialization, cannot be called a JWT.

Note

Further details on JWS and JWE are out of the scope of this book. Any interested readers who want a detailed walk-through of JWS and JWE should refer to this blog: “JWT, JWS and JWE for Not So Dummies!” at https://medium.facilelogin.com/jwt-jws-and-jwe-for-not-so-dummies-b63310d201a3 .

Let’s take a closer look at the following sample JWT:
eyJhbGciOiJSUzI1NiIsImtpZCI6Ijc4YjRjZjIzNjU2ZGMzOTUzNjRmMWI2YzAyOTA3NjkxZjJjZGZmZTEifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEwNTAyMjUxMTU4OTIwMTQ3NzMyIiwiYXpwIjoiODI1MjQ5ODM1NjU5LXRlOHFnbDcwMWtnb25ub21ucDRzcXY3ZXJodTEyMTFzLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJwcmFiYXRoQHdzbzIuY29tIiwiYXRfaGFzaCI6InpmODZ2TnVsc0xCOGdGYXFSd2R6WWciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXVkIjoiODI1MjQ5ODM1NjU5LXRlOHFnbDcwMWtnb25ub21ucDRzcXY3ZXJodTEyMTFzLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJ3c28yLmNvbSIsImlhdCI6MTQwMTkwODI3MSwiZXhwIjoxNDAxOTEyMTcxfQ.TVKv-pdyvk2gW8sGsCbsnkqsrS0T-H00xnY6ETkIfgIxfotvFn5IwKm3xyBMpy0FFe0Rb5Ht8AEJV6PdWyxz8rMgX2HROWqSo_RfEfUpBb4iOsq4W28KftW5H0IA44VmNZ6zU4YTqPSt4TPhyFC9fP2D_Hg7JQozpQRUfbWTJI
This looks gibberish until you break it by periods (.) and base64url-decode each part. There are two periods in it, which break the whole string into three parts (see Figure 11-4). Once you base64url-decode the fist part, it appears like so:
{"alg":"RS256","kid":"78b4cf23656dc395364f1b6c02907691f2cdffe1"}
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig4_HTML.jpg
Figure 11-4

JWS (compact serializing, representing a JWT)

This first part (once parted by the periods) of the JWT is known as the JOSE header. JOSE stands for JavaScript Object Signing and Encryption—and it’s the name of the IETF working group4 that works on standardizing the representation of integrity-protected data using JSON data structures.

The JOSE header indicates that it’s a signed message with the provided algorithm under the alg parameter. The token issuer asserts the identity of the end user by signing the JWT, which carries data related to the user’s identity. Both the alg and kid elements there are not defined in the JWT specification, but in the JSON Web Signature (JWS) specification. The JWT specification only defines two elements (typ and cty) in the JOSE header and both the JWS and JWE specifications extend it to add more appropriate elements.

Note

Both JWS and JWE compact serialization use base64url encoding. It is a slight variation of the popular base64 encoding. The base64 encoding defines how to represent binary data in an ASCII string format. Its objective is to transmit binary data such as keys or digital certificates in a printable format. This type of encoding is needed if these objects are transported as part of an email body, a web page, an XML document, or a JSON document.

To do base64 encoding, first the binary data is grouped into 24-bit groups. Then each 24-bit group is divided into four 6-bit groups. A printable character can represent each 6-bit group based on its bit value in decimal. For example, the decimal value of the 6-bit group 000111 is 7. As per Figure 11-5 the character H represents this 6-bit group. Apart from the characters shown in Figure 11-5, the character = is used to specify a special processing function, which is to pad. If the length of the original binary data is not an exact multiple of 24, then we need padding. Let’s say the length is 232, which is not a multiple of 24. Now we need to pad this binary data to make its length equal to the very next multiple of the 24, which is 240. In other words, we need to pad this binary data by 8 to make its length 240. In this case, padding is done by adding eight 0s to the end of the binary data. Now, when we divide 240 bits by 6 to build 6-bit groups, the last 6-bit group will be all zeros, and this complete group will be represented by the padding character =.

One issue with base64 encoding is that it does not work quite well with URLs. The + and / characters in base64 encoding (see Figure 11-5) have a special meaning when used within a URL. If we try to send a base64 encoded image as a URL query parameter and if the base64 encoded string carries either of these two characters, the browser will interpret the URL incorrectly. The base64url encoding was introduced to address this problem. It works exactly the same as base64 encoding other than two exceptions: the character - is used in base64url encoding instead of the character + and the character _ is used in base64url encoding instead of the character /.

../images/461146_1_En_11_Chapter/461146_1_En_11_Fig5_HTML.jpg
Figure 11-5

Base64 encoding

The second part of the JWT, as shown in Figure 11-4, is known as the JWT claim set (see Figure 11-6). Whitespace can be explicitly retained while building the JWT claim set—no canonicalization is required before base64url encoding or decoding. Canonicalization is the process of converting different forms of a message into a single standard form. This is used mostly before signing XML messages.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig6_HTML.jpg
Figure 11-6

JWT claim set

The JWT claim set represents a JSON object whose members are the claims asserted by the JWT issuer. Each claim name within a JWT must be unique. If there are duplicate claim names, then the JWT parser will either return a parsing error or just return the claims set with the very last duplicate claim. JWT specification does not explicitly define which claims are mandatory and which are optional. It’s up to the each application of JWT to define mandatory and optional claims. For example, the OpenID Connect specification defines the mandatory and optional claims. According to the OpenID Connect core specification, iss, sub, add, exp, and iat are treated as mandatory elements, while auth_time, nonce, acr, amr, and azp are optional elements. In addition to the mandatory and optional claims, which are defined in the specification, the token issuer can include additional elements into the JWT claim set.

The third part of the JWT (shown in Figure 11-4) is the signature, which is also base64url encoded. The cryptographic elements related to the signature are defined in the JOSE header. In this particular example, the token issued uses RSASSA-PKCS1-V1_5 with the SHA-256 hashing algorithm, which is expressed by the value of the alg element in the JOSE header: RS256. The signature is calculated against the first two parts in the JWS—the JOSE header and the JWT claim set.

Propagating Trust and User Identity

The user context from one microservice to another can be passed along with a JWS (see Figure 11-7). Since a key known to the calling microservice signs the JWS, it will carry both the end user identity (as claimed in the JWT) and the identity of the calling microservice (via the signature). In other words, the calling microservice itself is the issuer of the JWS. To accept the JWS, the recipient microservice first needs to validate the signature of the JWS against the public key embedded in the JWS itself or retrieved via any other mechanism. That’s not just enough — then it needs to check whether it can trust that key or not. Trust between microservices can be established in multiple ways. One way is to provision the trusted certificates, by service, to each microservice. It’s a no brainer to realize that this would not scale in a microservices deployment. The approach we would like to suggest is to build a private certificate authority (CA) and use intermediate certificate authorities by different microservices teams, if the need arises. Now, rather than trusting every individual certificate, the recipient microservices will only trust either the root certificate authority or an intermediary. That will vastly reduce the overhead in certificate provisioning.

Note

Trust bootstrap is a harder problem to solve. The Secure Production Identity Framework For Everyone (SPIFFE)5 project builds an interesting solution around this, which can be used to bootstrap trust between different nodes in a microservices deployment. With SPIFFE, each node will get an identifier and a key pair, which can be used to authenticate to other nodes it communicates with.

../images/461146_1_En_11_Chapter/461146_1_En_11_Fig7_HTML.jpg
Figure 11-7

Passing user context as a JWT between microservices

In the JWT, the public key corresponding to the key used to sign the token represents the caller (or the calling microservice). How does the recipient microservice find the end user information? The JWT carries a parameter called sub in its claim set, which represents the subject or the user who owns the JWT. If any microservice needs to identify the user during its operations, this is the attribute it should look into. The value of the sub attribute is unique only for a given issuer. If you have a microservice, which accepts tokens from multiple issuers, then the uniqueness of the user should be decided as a combination of the issuer and the sub attribute. In addition to the subject identifier, the JWT can also carry user attributes such as first_name, last_name, email, and so on.

Note

When we pass user context between microservices via a JWT, each microservice has to bear the cost of JWT validation, which also includes a cryptographic operation to validate the token signature. Caching the JWT at the microservices level against the data extracted out of it would reduce the impact of repetitive token validation. The cache expiration time must match the JWT expiration time. Once again, the impact of caching would be quite low if the JWT expiration time is quite low.

When issuing a JWT from a token issuer, it has to be issued to a given audience. The audience is the consumer of the token. For example, if the microservice foo wants to talk to the microservice bar, then the token is issued by foo (or a third party issuer), and the audience of the token is bar. The aud parameter in the JWT claim set specifies the intended audience of the token. It can be a single recipient or a set of recipients. Prior to any validation check, the token recipient must first see whether the particular JWT is issued for its use. If not, it should reject it immediately. The token issuer should know, prior to issuing the token, who the intended recipient (or the recipients) of the token is. The value of the aud parameter must be a pre-agreed value between the token issuer and the recipients. In a microservices environment, we can use a regular expression to validate the audience of the token. For example, the value of the aud in the token can be *.facilelogin.com, while each recipient under the facilelogin.com domain can have its own aud values: foo.facilelogin.com, bar.facilelogin.com, and so on.

Transport Layer Security (TLS) Mutual Authentication

Transport Layer Security (TLS) mutual authentication, also known as client authentication or two-way Secure Socket Layer (SSL), is part of the TLS handshake process. In one-way TLS, only the server proves its identity to the client; this is mostly used in e-commerce to win consumer confidence by guaranteeing the legitimacy of the e-commerce vendor. In contrast, mutual authentication authenticates both parties—the client and the server. In a microservices environment, TLS mutual authentication can be used between microservices to authenticate each other.

Both in TLS mutual authentication and with the JWT-based approach, each microservice needs to have its own certificates. The difference between the two approaches is that, in JWT-based authentication, the JWS can carry the end user identity as well as the upstream service identity. With TLS mutual authentication, the end user identity has to be passed at the application level—probably as an HTTP header.

Certificate Revocation

Both in TLS mutual authentication and with the JWT based approach, the certificate revocation is bit tricky. It is a harder problem to solve — though there are multiple options available: CRL (Certification Revocation List/RFC 2459), OCSP (Online Certificate Status Protocol / RFC 2560), OCSP Stapling (RFC 6066), and OCSP Stapling Required.

With CRL, the certificate authority (CA) has to maintain a list of revoked certificates. The client who initiates the TLS handshake has to get the long list of revoked certificates from the corresponding certificate authority and then check whether the server certificate is in the revoked certificate list. Instead of doing that for each request, the client can cache the CRL locally. Then you run into the problem that the security decisions are made based on stale data. When TLS mutual authentication is used, the server also has to do the same certificate verification against the client. The CRL is a not more often used technique. Eventually people recognized that CRLs are not going to work and started building something new, which is the OCSP.

In the OCSP world, the things are little bit better than CRL. The TLS client can check the status of a specific certificate without downloading the whole list of revoked certificates from the certificate authority. In other words, each time the client talks to a new downstream microservice, it has to talk to the corresponding OCSP responder6 to validate the status of the server (or the service) certificate — and the server has to do the same against the client certificate. That creates some extensive traffic on the OCSP responder. Once again clients still can cache the OCSP decision, but then again it will lead to the same old problem of making decisions on stale data.

With OCSP stapling , the client does not need to go to the OCSP responder each time it talks to a downstream microservice. The downstream microservice will get the OCSP response from the corresponding OCSP responder and staple or attach the response to the certificate. Since the corresponding certificate authority signs the OCSP response, the client can accept it by validating the signature. This makes things little better. Instead of the client, the service has to talk to the OCSP responder. But in a mutual TLS authentication model, this won’t bring any additional benefits when compared to the plain OCSP.

With OCSP must stapling, the service (downstream microservice) gives a guarantee to the client (upstream microservice) that the OCSP response is attached to the service certificate it receives during the TLS handshake. In case the OCSP response is not attached to the certificate , rather than doing a soft failure, the client must immediately reject the connection.

Short-Lived Certificates

From the end user perspective the short-lived certificates behave the same way as the normal certificates work today, they just have a very short expiration. The TLS client needs not to worry about doing CRL or OCSP validations against short-lived certificates and rather sticks to the expiration time, stamped on the certificate itself.

The challenge in short-lived certificates mostly lies in their deployment and maintenance. Automation is the goddess of rescue! Netflix suggests using a layered approach (see Figure 11-8) to build a short-lived certificate deployment. You would have a system identity or long-lived credential that resides in a TPM (Trusted Platform Module) or an SGX (Software Guard Extension) having lot of security on it. Then use that credential to get a short-lived certificate. Then use the short-lived certificate for your microservice, which would be consumed by another microservice. Each microservice can refresh the short-lived certificates regularly using its long-lived credentials. Having the short-lived certificate is not enough—the underlying platform, which hosts the service (or the TLS terminator), should support dynamic updates to the server certificate. A lot of TLS terminators out there support dynamically reloading the server certificates, but not with zero downtime in most cases.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig8_HTML.jpg
Figure 11-8

How Netflix uses short-lived certificates

The Edge Security

In Chapter 7, “Integrating Microservices” and Chapter 10, “APIs, Events, and Streams,” we discussed different techniques for exposing microservices to the rest of the world. One common approach discussed there was to use the API gateway pattern . With the API gateway pattern (see Figure 11-9) — the microservices, which need to be exposed outside, would have a corresponding API in the API gateway. Not all the microservices need to be exposed from the API gateway.

The end user’s access to the microservices (via an API) should be validated at the edge — or at the API gateway. The most common way of securing APIs is OAuth 2.0. Over time, OAuth 2.0 has become the de-facto standard for API security.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig9_HTML.jpg
Figure 11-9

The API gateway pattern

OAuth 2.0

OAuth 2.0 is a framework for access delegation. It lets someone do something on behalf of someone else. There are four main characters in an OAuth 2.0 flow: the client, the authorization server, the resource server, and the resource owner. Let’s say you build a web application that lets users export their Flickr photos into it. In that case, your web application has to access the Flickr API to export photos on behalf of the users who actually own the photos. There the web application is the OAuth 2.0 client, Flickr is the resource server (which holds its users’ photos), and the Flickr user who wants to export photos to the web application is the resource owner. For your application to access Flickr API on behalf of the Flickr user, it needs some sort of an authorization grant. The authorization server issues the authorization grant, and in this case it’ll be Flickr itself. But, in practice there can be many cases where the authorization server and the resource server are two different entities. OAuth 2.0 does not couple those two together.

Note

An OAuth 2.0 client can be a web application, a native mobile application, a single page application, or even a desktop application. Whoever the client is, it should be known to the authorization server. Each OAuth client has an identifier, which is known as a client ID, given to it by the authorization server. Whenever a client communicates with authorization server, it has to pass its client ID. In some cases, client has to use some kind of credentials to prove who it is. The most popular form of credentials is the client secret. It is like a password. But it is always recommended that OAuth clients use stronger credentials, like certificates or JWTs.

OAuth 2.0 introduces multiple grant types. A grant type in OAuth 2.0 explains the protocol; the client should get the resource owner’s consent to access a resource on his behalf. Also, there are some grant types that define a protocol to get a token, just on behalf of himself (client_credentials) — in other words, the client is also the resource owner. Figure 11-10 illustrates OAuth 2.0 protocol at a very high-level. It describes the interactions between the OAuth client, the resource owner, the authorization server, and the resource server.

Note

The OAuth 2.0 core specification (RFC 67497) defines five grant types: authorization code, implicit, password, client credentials, and refresh. The authorization code grant type is the most popular grant type, used by more than 70% of the web applications. In fact, it is the recommended grant type for many of the use cases, whether you have a web application, a native mobile application, or even a single page application (SPA).

../images/461146_1_En_11_Chapter/461146_1_En_11_Fig10_HTML.jpg
Figure 11-10

The OAuth 2.0 protocol

Whoever wants to access a microservice via the API gateway must get a valid OAuth token first (see Figure 11-11). A system can access a microservice, just by being itself — or on behalf of another user. For the latter case, an example would be when a user logs in to a web application and the web application accesses a microservice on behalf of the user who logged in. When a system wants to access an API on behalf of another user, authorization code is the recommended grant type. In other cases where a system accesses an API by being itself, we can use the client credentials grant type.

Note

There are two types of OAuth 2.0 access tokens: reference tokens and self-contained tokens. A reference token is an arbitrary string issued by the authorization server to the client application to be used against a resource server. It must have a proper length and should be unpredictable. Whenever a resource server sees a reference access token, it has to talk to the corresponding authorization server to validate it. A self-contained access token is a signed JWT (or a JWS). To validate a self-contained access token, the resource server does not need to talk to the authorization server. It can validate the token by validating its signature.

../images/461146_1_En_11_Chapter/461146_1_En_11_Fig11_HTML.jpg
Figure 11-11

End-to-end authentication flow

Let’s see how end-to-end communication works, as illustrated in Figure 11-11:
  1. 1.

    The user logs into the web app/mobile app via the identity provider, which the web app/mobile app trusts via OpenID Connect (this can be SAML 2.0 too). OpenID Connect is an identity federation protocol built on top of OAuth 2.0. SAML 2.0 is another similar identity federation protocol.

     
  2. 2.

    The web app gets an OAuth 2.0 access_token, a refresh_token, and an id_token. The id_token will identify the end user to the web app. OpenID Connect introduces the id_token to the OAuth flow. If SAML 2.0 is used, then the web app needs to talk to the token endpoint of the OAuth authorization server it trusts and exchange the SAML token to an OAuth access_token, following the SAML 2.0 grant type for OAuth 2.0. Each access_token has an expiration, and when the access_token is expired or close to expiration, the OAuth client can use the refresh_token to talk to the authorization (no need to have the end user) and get a new access_token.

     
  3. 3.

    The web app invokes an API on behalf of the end user — passing the access_token along with the API request.

     
  4. 4.

    API gateway intercepts the request from the web app, extracts the access_token, and talks to the Token Exchange endpoint (or the STS), which will validate the access_token and then issue a JWT (signed by it) to the API gateway. This JWT will also carry the user context. While STS validating the access_token it will talk to the corresponding OAuth authorization server via an API (Introspection API as defined by the RFC 76628).

     
  5. 5.

    The API gateway will pass through the JWT along with the request to the downstream microservices.

     
  6. 6.

    Each microservice will validate the JWT it receives and then, for the downstream service calls, it can create a new JWT signed by itself and send it along with the request. Another approach is to use a nested JWT — so the new JWT will also carry the previous JWT. Also, there is third approach, where each microservice talks to the security token service and exchanges the token it got for a new token, to talk to the other downstream microservices.

     

Note

A detailed explanation of OAuth 2.0 and OpenID Connect is out of the scope of this book. We encourage interested readers to go through the book, Advanced API Security, written by one of the authors of this book and published by Apress.

With this approach , only the API calls coming from the external clients will go through the API gateway. When one microservice talks to another ,  that needs not to go through the gateway. Also ,  from a given microservice perspective, whether you get a request from an external client or another microservice, what you get is a JWT — so this is a symmetric security model.

Access Control

Authorization is a business function. Each microservice can determine the criteria to allow access to its operations. In the simplest form of authorization, we check whether a given user can perform a given action on a particular resource. The combination of an action and a resource is termed as the permission. An authorization check evaluates whether a given user has the minimum set of required permissions to access a given resource. The resource can define who can perform and which actions they can perform. The declaration of the required permissions for a given resource can be done in multiple ways. The most common way is to attach a policy or an access control list (ACL) to the resource. There are multiple policy languages being used to express these access control requirements. If you are familiar with Amazon Web Services (AWS), you might have noticed the quite simple, but strong, JSON-based policy language9 used there, as shown here.
{
  "Version": "2012-10-17",
  "Statement": {
        "Effect": "Allow",
        "Action": "s3:ListBucket",
        "Resource": "arn:aws:s3:::example_bucket"
  }
}
Open Policy Agent10 (OPA) introduces another policy language mostly targeting policy-based control for cloud native environments. The following shows a sample policy defined in OPA, which allows access to all HTTP requests.
package http.authz
allow = true

XACML (eXtensible Access Control Markup Language) provides another way of defining access control policies. It is so far the only standard (by OASIS) out there for a policy language. The following section delves deeply into XACML.

XACML (eXtensible Access Control Markup Language)

XACML is the de-facto standard for fine-grained access control. It introduces a way to represent the required set of permissions to access a resource, in a very fine-grained manner in an XML-based domain-specific language (DSL).

XACML provides a reference architecture, a request response protocol, and a policy language. Under the reference architecture, it talks about a Policy Administration Point (PAP), a Policy Decision Point (PDP), a Policy Enforcement Point (PEP), and a Policy Information Point (PIP). This is a highly distributed architecture in which none of the components is tightly coupled. The PAP is the place where you author policies. The PDP is the place where policies are evaluated and decisions are made. While evaluating policies, if there is any missing information that can’t be derived from the XACML request, the PDP calls the PIP. The role of the PIP is to feed the PDP any missing information, which can be user attributes or any other required details. The policy is enforced through a PEP, which sits between the client and the service and intercepts all requests. From the client request, it extracts certain attributes such as the subject, the resource, and the action; then it builds a standard XACML request and calls the PDP. Then it gets a XACML response from the PDP. That is defined under the XACML request/response model. The XACML policy language defines a schema to create XACML policies for access control.

Figure 11-12 shows the XACML component architecture. The policy administrator first needs to define XACML policies via the PAP (Policy Administration Point) and those policies will get stored in the policy store. To check whether a given entity has the permission to access a given resource, the PEP (Policy Enforcement Point) has to intercept the access request, create a XACML request, and send it to the XACML PDP (Policy Decision Point). The XACML request can carry any attributes that could help the decision-making process at the PDP.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig12_HTML.jpg
Figure 11-12

XACML component architecture

For example, it can include the subject identifier, the resource identifier, and the action the given subject is going to perform on the resource. The microservice that needs to authorize the user has to build a XACML request by extracting the relevant attributes from the JWT and talk to the PDP. The PIP (Policy Information Point) comes into the picture when the PDP finds that certain attributes required for policy evaluation are missing in the XACML request. Then the PDP will talk to the PIP to find the missing attributes. The PIP can connect to relevant datastores, find the attributes, and then feed those into the PDP.

Note

XACML is an XML-based open standard for policy-based access control developed under the OASIS XACML technical committee. The latest XACML 3.0 specification was standardized in January 2013. See www.oasis-open.org/committees/tc_home.php?wg_abbrev=xacml .

There are two main ways to bring in a policy decision point (PDP) to the microservices architecture. In fact PDP is an implementation-agnostic term; it does not have a deep coupling to the policy language in terms of the architectural perspective. As shown in Figure 11-13, one way is to treat the PDP as a single, remote endpoint, where all the microservices connect to authorize the access requests. Each microservice will create its own XACML request and pass it over a communication channel to the PDP. PDP evaluates the request against the corresponding policies and sends back the XACML response.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig13_HTML.jpg
Figure 11-13

Centralized/remote XACML PDP

Figure 11-14 shows an example XACML request in JSON. Here we can assume that someone invokes the bar microservice to buy a beer. The bar microservice extracts the subject or the user who invokes the service from the JWT coming along with the request and builds the XACML request accordingly.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig14_HTML.jpg
Figure 11-14

XACML request in JSON

Note

With the increasing popularity and adaptation of APIs, it becomes crucial for XACML to be easily understood in order to increase the likelihood that it will be adopted. XML is often considered too verbose. Developers increasingly prefer a lighter representation using JSON, the JavaScript Object notation. The profile “Request / Response Interface based on JSON and HTTP for XACML 3.0” aims at defining a JSON format for the XACML request and response.

See https://www.oasis-open.org/committees/document.php?document_id=47775 .

Embedded PDP

There are certain drawbacks to the remote or the centralized PDP model that could easily violate base microservices principles:
  • Performance cost : Each time we need to do an access control check, the corresponding microservice has to talk to the PDP over the wire. With decision caching at the client side, the transport cost and the cost of the policy evaluation can be cut down. But with caching, we will make security decisions based on stale data.

  • The ownership of policy information points (PIP) : Each microservice should have the ownership of its PIPs, which know where to bring in the data required to do the access controlling. With this approach we are building a centralized PDP, which has all the PIPs   corresponding to all the microservices.

  • Monolithic PDP : The centralized PDP becomes another monolithic application. All the policies associated with all the microservices are stored centrally in the monolithic PDP. Introducing changes is hard as a change to one policy may have an impact on all the policies, since all the policies are evaluated under the same policy engine.

As illustrated in Figure 11-15, the embedded PDP will run along with each microservice. It follows an eventing model for policy distribution, where each microservice will subscribe to its interested topics to get the appropriate access control policies from the PAP and then update the embedded PDP. You can have PAPs by microservices teams or one globally in a multi-tenanted mode. When a new policy is available or when there is a policy update, the PAP will publish an event to the corresponding topics. The embedded PDP model introduces a message broker to the microservices architecture.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig15_HTML.jpg
Figure 11-15

Embedded XACML PDP

This approach does not violate the immutable server concept in microservices. Immutable server means  that you build servers or containers directly out of configuration loaded from a repository at the end of the continuous delivery process   and you should be able to build the same container again and again with the same configuration. So ,  we would not expect anyone to log in to a server and do any configuration changes there. With the embedded PDP model — even though the server loads the corresponding policies while it’s running — if we spin up a new container it too gets the same set of policies from the corresponding PAP.

There is an important question that’s still unanswered. What is the role of the API gateway under the context of authorization? We can have two levels of policy enforcements. One is globally for all the requests going through the API gateway (which will act as the policy enforcement point or the PEP), and the other one is at the service level. The service-level policies must be enforced via some kind of an interceptor at the container or the service level.

Security Sidecar

We introduced the concept of sidecar in the Chapter 2, “Designing Microservices”. Let’s quickly recap. As shown in Figure 11-16, the sidecar pattern is derived from the vehicle where a sidecar is attached to a motorcycle. If you’d like you can attach different sidecars (of different colors or designs) to the same motorcycle, provided that the interface between those two is unchanged. The same applies in the microservices world, where our microservice resembles the motorcycle, while the security processing layer resembles the sidecar. The communication between the microservice and the sidecar happens over a remote channel (not a local, in-process call), but both the microservice and the sidecar will be deployed in the same physical/vitual machine, so it will not be routed over the network. Also, keep in mind that the sidecar itself is another microservice.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig16_HTML.png
Figure 11-16

Sidecar

Note

As discussed in Chapter 9, Istio11 introduces a sidecar proxy for supporting strong identity, powerful policy, transparent TLS encryption, and authentication, authorization, and audit (AAA) tools to protect your services and data. Istio Citadel provides strong service-to-service and end-user authentication with built-in identity and credential management, based on the sidecar and control plane architecture.

There are many benefits of implementing security as a sidecar in the microservices architecture. The following list explains some of them.
  • The microservice implementation does not need to worry about the internals of the security implementation and those need not to be in the same programming language.

  • When implemented as a sidecar, the security functionality can be reused by other microservices, without worrying about individual implementation details.

  • The ownership of the security sidecar can be taken care by a different team that has expertise on the security domain, other than the microservices developers, who worry about domain-specific business functionalities.

Let’s see how the security sidecar fits into the microservices architecture. As shown in Figure 11-17, there can be four main functionalities of a security sidecar: token validation (introspection), get user info (userinfo), token issuance (token), and authorize access requests (pdp). Each microservice should have its own interceptor that intercepts all the requests and talk to the token validation endpoint to check whether the provided token is good enough to access the corresponding microservice. To make the sidecar interoperable, it is recommended to expose the OAuth 2.0 introspection endpoint12 to validate the tokens. This endpoint will accept a token in the request and will reply with a JSON payload, which includes information related to the provided token.
../images/461146_1_En_11_Chapter/461146_1_En_11_Fig17_HTML.jpg
Figure 11-17

Security sidecar

The following text shows a request to the introspection endpoint and a response from it.
POST /introspect HTTP/1.1
Host: sidecar.local
Accept: application/json
Content-Type: application/x-www-form-urlencoded
token=2YotnFZFEjr1zCsicMWpAA
HTTP/1.1 200 OK
Content-Type: application/json
     {
      "active": true,
      "client_id": "l238j323ds-23ij4",
      "username": "jdoe",
      "scope": "read write dolphin",
      "sub": "jdoe",
      "aud": "https://foo.com",
      "iss": "https://issuer.example.com/",
      "exp": 1419356238,
      "iat": 1419350238
     }
Once the token is validated, if the microservice wants to find more information about the end user who invoked the microservice, it can talk to the userinfo endpoint exposed by the security sidecar. This is another standard endpoint defined in the OpenID Connect specification13. This endpoint accepts a request with a valid token and returns the associated user’s information in a JSON message. The following text shows a request to the userinfo endpoint and a response from it.
GET /userinfo HTTP/1.1
Host: sidecar.local
Authorization: Bearer SlAV32hkKG
HTTP/1.1 200 OK
Content-Type: application/json
  {
   "sub": "jdoe",
   "name": "Jane Doe",
   "given_name": "Jane",
   "family_name": "Doe",
   "preferred_username": "j.doe",
   "email": "[email protected]",
  }
The pdp endpoint can be called either by an interceptor or within the microservice itself to find out whether the incoming request is good enough to perform the action it is intended to perform. This can be standardized over the JSON profile for XACML14 and the REST profile for XACML15. Even though we standardized the request and response over XACML, that does not necessarily mean that we have to maintain access control policies in the sidecar in XACML. It can be in any way we want. The following text shows a request to the pdp endpoint and the response from it.
POST /pdp HTTP/1.1
Host: sidecar.local
Accept: application/json
Content-Type: application/x-www-form-urlencoded
[xacml request in json, see figure 11-4]
HTTP/1.1 200 OK
Content-Type: application/json
{
    "Response": [{
        "Decision": "Permit"
     ]]
}
The token endpoint exposed by the security sidecar can be called by the microservice to get a new token, which is good enough to talk to another downstream microservice. This endpoint can be standardized under the OAuth 2.0 token endpoint, with support for the OAuth 2.0 Token Exchange16 profile. The request to the token endpoint will carry the original token and an identifier representing the downstream microservice it wants to talk to. In response we get a new token. The following text shows a request to the token endpoint and the response from it.
POST /token HTTP/1.1
Host: sidecar.local
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&resource=foo
&subject_token=SlAV32hkKG
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store
{
     "access_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6Ijllc",
     "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
     "token_type":"Bearer",
     "expires_in":60
}

Summary

In this chapter, we discussed the common patterns and fundamentals related to securing microservices. Securing service-to-service communication is the most critical part in securing microservices, where we have two options with JWT and certificates. Edge security is mostly handled by the API gateway with OAuth 2.0. There are two models in access controlling microservices: centralized PDP and embedded PDP. Toward the end of the chapter, we also discussed the value of a security sidecar in the microservice architecture. Understanding the fundamentals of securing microservices is a key to build a production-ready microservices deployment. In the next chapter, we discuss how to implement security for microservices using Spring Boot.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset