The explanation given makes sense. If they're operating on the same data, especially if the result goes to the same consumer, are they really different services? On the other hand, if the shared service provides different data to each, is it really one microservice or has it started to become a tad monolithic in that it's one service performing multiple functions?
I like that the author provides both solutions: join (my preferred) or split the share.
I don't understand this. Can you help explain it with a more practical example? Say that N1 (the root service) is a GraphQL API layer or something. And then N2 and N3 are different services feeding different parts of that API—using Linear as my example, say we have a different service for ticket management and one for AI agent management (e.g. Copilot integration). These are clearly different services with different responsibilities / scaling needs / etc.
And then N4 is a shared utility service that's responsible for e.g. performance tracing or logging or something similar. To make the dependency "harder", we could consider that it's a shared service responsible for authentication and authorization. So it's clear why many root services are dependent on it—they need to make individual authorization decisions.
How would you refactor this to remove an undirected dependency loop?
Yeah, a lot of cross-cutting concerns fall into this pattern: logging, authorization, metrics, audit trails, feature-flags, configuration distribution, etc
The only way I can see to avoid this is to have all those cross-cutting concerns handled in the N1 root service before they go into N2/N3, but it requires having N1 handle some things by itself (eg: you can do authorization early), or it requires a lot of additional context to be passed down (eg: passing flags/configuration downstream), or it massively overcomplicates others (eg: having logging be part of N1 forces N2/N3 to respond synchronously).
So yeah, I'm not a fan of the constraint from TFA. It being a DAG is enough.
I think this philosophy only reasonably applies behind the public-facing API gateway. So the GraphQL API server wouldn't be part of the microservice graph that you're trying to make into a polytree (you also wouldn't consider the client-side software to be part of this graph). You can use GraphQL delegation or similar to move more responsibility to the other side of the line.
The only alternative I can think of is to have a zillion separate public-facing API servers on different subdomains, but that sounds like a headache.
I tried and cannot. Just keep thinking of it as: if something is doing 2 jobs, split it, if 2 things have the same as they say goes-in-tos and -goes-out-ofs, combine them. And same doesn't mean bit for bit match (though obviously don't needlessly duplicate data), but just a bit higher level.
The problem is that I don't sit in the microservice or enterprise backend spaces, so I an struggling to formulate explanations in those terms.
Most components need to depend on an auth service, right? I don’t think that means it’s all necessarily one service (does all of Google Cloud Platform or AWS need to be a single service)?
That's immediately what I thought of. You'll never be able to satisfy this rule when every service has lines pointing to auth.
You'll probably also have lines pointing to your storage service or database even if the data is isolated between them. You could have them all
be separate but that's a waste when you can leverage say a big ceph cluster.
The trick I've used is the N1 (gateway) service handles all AuthN and proxies that information to the upstream services to allow them to handle AuthZ. N+ services only accept requests signed by N1 - the original authentication info is removed.
I think it does indeed make a lot of sense in the particular example given.
But what if we add 2 extra nodes: n5 dependent on n2 alone, and n6 dependent on n3 alone? Should we keep n2 and n3 separate and split n4, or should we merge n2 and n3 and keep n4, or should we keep the topology as it is?
The same sort of problem arises in a class inheritance graph: it would make sense to merge classes n2 and n3 if n4 is the only class inheriting from it, but if you add more nodes, then the simplification might not be possible anymore.
Even with periodoc rotation of credentials, attacker gets enough time to do sufficient damage. Imo, the best way to solve would be to not handle any sort of credentials at all at the application layer! If at all the application must only handle only very short lived tokens. Let there be a sidecar (for example) that does the actual credential injection.
Whats the most full proof way of defending ourselves from such attacks? My opinion is that the applications should never deal with credentials at all. Sidecars can be run which can inject credentials in real time. These sidecars can be under tight surveillance against such attacks. After all, application code is the most volatile in an organization.
I think we shouldn't be using package repositories in this way at all, shouldn't it be much better to have a package system like golang has where you directly import the sourcecode from github? You get around an entire class of problems. At least now you can only be compromised if the github source code itself is compromised, not any part of some build pipeline or a tool like npm or an npm registry. That means to vendor everything and only upgrade if you need to upgrade, treat all the code like you are responsible for it all because you are. The entire concept of relying on builds of other people is part of the problem, it's bad enough that we rely on source code of other people but that goes with the territory. Relying on their build systems is not as mandatory.
pnpm’s minimumReleaseAge can help a ton with this. There’s a tricky balance, because allowing your dependencies to get stale makes you inherently more vulnerable to vulnerabilities in your packages. And, critically, fixing a vulnerability in an urgent situation (i.e. you were compromised) gets increasingly harder to address the more stale your dependencies are.
minimumReleaseAge strikes a good balance between protecting yourself against emerging threats like Shai-Hulud and keeping your dependencies up-to-date.
Because you asked: you can get another layer of protection through Socket Firewall Free (sfw), which prevents dependencies known to be malicious from being installed. Socket typically identifies malware very soon after its is published. Disclaimer: I’m the lead dev on the project, so obviously biased — YMMV.
Whats the most full proof way of defending ourselves from such attacks? My opinion is that the applications should never deal with credentials at all. Sidecars can be run which can inject credentials in real time. These sidecars can be under tight surveillance against such attacks. After all, application code is the most volatile in an organization.
To me this is asking the question of "what's the safest way to drink from a polluted river".
The answer is really, don't.
NPM and the JS eco-system has really gone down a path of zero security and they're paying the price for it.
If you really need libraries from NPM and whatnot, vendorize them so you're relying on known-safe files and don't arbitrarily update them without re-verification.
This looks amazing!
I have a question. When we create a branch, are we cloning the entire production data? From what I understood was that neon runs a separate compute layer but the storage is the same.
Neon's engineer here: If you're asking about how branches work in general, they use Copy-on-Write (CoW), which means that no data is copied when they are created. However, when writes occur in a branch, only the modified specific Postgres pages are copied (working like deltas in your data), which is why they are created very quickly and don't increase storage usage when created, increasing only in the modified portion.
If you're asking about how the anonymized branches work, they currently use static masking, which means that all PII data selected for masking will be overridden in the anonymized branch, resulting in increased storage usage based on the amount of selected masked data. However, the team is also working on dynamic masking, which doesn't change the stored data but applies the masking rules when querying it.
I am working on a platform that simplifies access control in an enterprise.
A lot of cybersecurity attacks happen because of stolen credentials. One big example is the supply chain attack, Shai Hulud. In a lot of enterprises, credential sprawl is a huge issue and figuring out who (people, services, ai agents) has access to to what systems is a paramount task.
At https://gearsec.io, we are building a platform where accesses are created via policies. The result is that, the enterprise doesnt deal with credentials anymore. They only need to define policies and nothing more.
I would love to know if you faced this problem and how are you solving them at your workplace!
reply