18 Security System How to Establish a Reliable Security System

This appears to be the front matter of a Markdown file. The “weight” property mentioned here is commonly used in static site generators to determine the order or priority of content. The value of “19” suggests that this particular content might have a higher priority or weightage than other content.

18 Security System - How to establish a reliable security system #

Hello, I’m He Xiaofeng. In the previous lecture, we learned how to improve the utilization of resources in a single machine in RPC. The key point you need to remember is “asynchrony”. The caller utilizes the asynchronous mechanism to make parallel calls to multiple services, reducing the overall call time. Meanwhile, the service provider can use asynchrony to execute business logic in a custom thread pool, improving the OPS of a single machine.

After reviewing the key points of the previous lecture, let’s dive into today’s topic and take a look at the security issues in RPC.

Why do we need to consider security issues? #

When it comes to security issues, you might think of malicious attacks like SQL injection, XSS attacks, and more broadly, security issues such as network security and information security. So, what does security mean in the context of RPC?

We know that RPC is a framework for inter-application communication. The remote invocation process between applications is generally not exposed to the public internet. In other words, RPC is usually used to solve communication between internal applications, where “internal” refers to applications deployed within the same LAN. Compared to a public network environment, a LAN provides better isolation and therefore relative security. Therefore, in RPC, we rarely consider malicious behaviors like packet tampering or request forgery.

So, what security issues should we be concerned about in RPC? To understand this question, let’s first look at a complete RPC application process.

Usually, the service provider defines an interface and publishes the corresponding JAR file to a private server. Then, in the project, the interface is implemented and exposed to the outside world through the API provided by RPC. In a Spring application, it can be defined as a bean. With this, the service provider has completed the external publication of an interface.

For the service caller, it’s even simpler. They only need to obtain the coordinates of the JAR file uploaded to the private server, import the JAR file into the project, and then use the dynamic proxy feature provided by RPC to directly make RPC calls in the project.

There is a security vulnerability in this process: everyone can see all the JAR coordinates on the private server. Can anyone import the published JAR file into their project and make RPC calls without consultation?

In theory, yes. However, I believe that in a company, such behavior of directly calling without consulting the service provider rarely occurs. Moreover, real business interfaces usually have complex input and output parameters, making it inefficient to rely on guessing for calling without consultation.

Although the probability of guessing-based calls is small, when the service caller needs to use an interface previously used in another business scenario, they might actually call it without informing the service provider. This behavior is dangerous for the service provider because adding a new caller means an increase in call volume. Sometimes, the newly added call volume can become the “last straw” that overwhelms the service provider, causing them to be unable to provide services. The crucial issue is that the service provider does not know who is overwhelming them.

Of course, you might say that this is a process issue and we can avoid this problem by standardizing the calling process within the company.

Indeed, we can use process communication to achieve a “gentleman’s agreement” among all our developers. This agreement stipulates that whenever an interface is used in an application, it must be reported to the service provider beforehand. This can greatly reduce the occurrence of such situations. However, from the perspective of RPC itself, can’t we provide some functionality to solve this issue? After all, even with a verbally agreed process in place, there are still risks and uncertainties in large teams.

Security guarantee among callers #

So, how do we solve this problem in RPC?

Let’s summarize the problem we just discussed. The root cause is that the service provider does not know which caller initiated the request, so they cannot determine whether the request belongs to a caller that has greeted them before or a new caller. As a result, they cannot decide whether to reject or execute the request.

Once the problem is clear, the solution becomes apparent. We simply need to assign a unique identity to each caller. Before making a call, each caller needs to register their identity with the service provider. Only registered callers will be allowed to proceed, while unregistered callers will be rejected.

This is similar to how we buy train tickets in real life. We present our ID card to purchase a train ticket, which is similar to callers registering with the service provider. When we enter the station and get on the train, we must present both our ID card and the train ticket, which represent our “unique identity” to board the train. Only when our identity has been verified by the ticket inspector will we be allowed to board the train; otherwise, we will be rejected.

Now that we have a solution, how do we implement it in RPC?

First, we need a place where callers can register and manage their interface calls. Let’s call this place the “authorization platform”. Callers can apply for the interfaces they want to call within their applications on the authorization platform, and the service provider can review and approve these requests. Only after the service provider has approved a request can the caller make the call. However, this solution only addresses the issue of collecting call data and does not provide true authentication functionality. It lacks a verification step.

Since we have set up the authorization platform and the authorization data for the interfaces is stored there, it makes sense to add the verification step to the authorization platform. Each time a caller initiates a business request, they first send an authentication request to the authorization platform, asking, “Can I call this interface?” The caller can only proceed with sending the business request to the service provider if the authorization platform responds with “no problem”. The overall process is shown in the following diagram:

From a functional perspective, this design currently works well, and the entire authentication process is transparent to RPC users. However, there is one issue: the authorization platform bears the cumulative number of RPC requests within the company. If the internal RPC usage increases significantly, the authorization platform may become a bottleneck, and its high availability must be ensured. Any issues with the authorization platform would affect all RPC requests within the company.

You might suggest an improvement: instead of including the authentication logic in the business request process, can we move this verification process to the initialization process? This would indeed reduce the load on the authorization platform to a large extent, but the fundamental centralized nature of the authorization platform remains unchanged.

Let’s consider a more elegant solution.

In fact, whether a caller can call a particular interface is determined by the service provider. If the service provider approves, the caller can definitely make the call. So, can we move the ticket verification process to the service provider? During the initialization of the caller, they can include the identity issued by the authorization platform and authenticate it with the service provider. Once the authentication is successful, the service provider considers the interface callable.

Now we have a new problem. Where does the service provider get the data for ticket verification? It wouldn’t make sense to request it from the authorization platform again, as it would encounter the same problem as in the previous solution.

Do you remember the irreversible encryption algorithms we discussed? HMAC is one such implementation. The service provider’s application can store a private key used for HMAC signature, and the authorization platform can use this key to sign the calling application’s request, resulting in a unique identity for the caller. After receiving an authorization request from the caller, the service provider only needs to verify whether the signature matches the calling application’s information. This way, we address the bottleneck of the centralized authorization model.

Are there security issues with service discovery? #

Alright, now we have resolved the security authentication issues between the invokers. But, do we still have other security issues within RPC?

Going back to the complete RPC application process we mentioned earlier, the service provider will publish the interface JAR to a private repository to allow the invokers to quickly import and complete RPC calls in their projects. Is it possible for someone to take this JAR and publish a fake service provider? The consequence of this would be that the invokers would have the address collection of the forged provider in the services discovered.

Of course, the probability of this situation occurring is relatively small compared to the invokers directly calling without consultation, but in order to make our system as a whole more secure, we also need to consider this situation in RPC. The fundamental solution to this problem is to bind the interface to the application. Only one application should be allowed to publish a provider for a specific interface, preventing other applications from publishing the same interface.

So, how do we implement this? In Lesson 08, we mentioned that when the service provider starts, it needs to register the interface instance with the registry center. We can utilize this process. When the registry center receives a registration request from a service provider, it can verify whether the application that the request comes from is the same as the one bound to the interface. Only if they are the same will the registration be allowed; otherwise, an error message will be returned to the starting application, thereby preventing counterfeit service providers from providing incorrect services externally.

Summary #

Security issues are important in any field, but they are often overlooked. We only realize the importance of security protection after each security incident. Therefore, in the process of daily coding, we must maintain a rigorous attitude to prevent small errors from introducing security issues online.

Although RPC is often used to solve the calling between intranet applications, the intranet environment is relatively less severe than the Internet. However, it is necessary to establish a controllable security system to prevent certain malicious behaviors. For RPC, the security issues we care about are not as complex as those of public Internet applications. We just need to ensure that the service consumers can obtain the real list of service provider IP addresses, and the service providers can control the calls to their own applications.

After-class Reflection #

In the previous discussion on security issues among invokers, we mainly focused on solving authentication problems and didn’t address authorization issues. In real development scenarios, an RPC interface usually contains multiple methods. However, we have only solved the problem of whether you can invoke the interface, and have not addressed which methods within my interface you can invoke.

Do you have any good solutions for this issue?

Feel free to leave a comment and share your answer with me. You are also welcome to share this article with your friends and invite them to join the learning journey. See you in the next lesson!