Securing Cloud-Native Communication, From End-User to Service

Learn how service meshes are uniquely equipped to enforce mutual TLS and secure service communication with no gaps.

Good security makes it easy to do the right thing. That's why more and more companies operating in cloud-native environments are looking to service meshes to address microservice network security and other modern threat models.

For this talk, Datawire's Daniel Bryant will discuss end-to-end communication and higher-level networking threats such as man-in-the-middle attacks. These attacks can be thwarted with the combination of an edge proxy and service mesh using TLS and mTLS.

Key takeaways include: - The "three pillars" of service mesh functionality - observability, reliability, and security - A service mesh is in a unique place to enforce security features like mTLS - How to ensure that there are no exploitable "gaps" within the end-to-end/user-to-service communication path - Explore the differences in ingress/mesh control planes, with brief demonstrations using and Consul Connect.

Be sure to also check out Daniel Bryant's recap blog from HashiConf EU 2019.



  • Daniel Bryant
    Daniel BryantProduct Architect, Datawire, Ambassador Labs


Welcome to “Securing Cloud-Native Communication.” I’m going to start by riffing on something Mitchell Hashimoto said at the beginning of the conference. He talked a lot about the enterprise focus, with security and communications at the edge. We harden the perimeter. He used a more friendly term, perhaps, than I’m going to use, but a lot of folks say this is what we call “Death Star security.” You focus on making a really strong perimeter but, as we know from Star Wars, if a womp rat-sized hole is left, someone can get in and do a lot of damage.

I’m not saying focusing on the edge is bad. It’s actually very good as part of a defense-in-depth strategy, but you also need to make sure your internal security is good, as well. A strong front door is no good if you’re letting people in through the side. And lastly, we have to make it easy to do the right thing. I say this time and time again. But, in particular, with security, if you don’t make it easy to do the right thing, people are going to bypass stuff.

As engineers, if we’ve got a challenge, we will do it any way we can. Who hasn’t put the security groups to ‘enable all’? Who hasn’t disabled network ACLs sometimes? We’ve all done that thing.

If you take away nothing else, these are the 5 most important things in the talk today:

  • Security is everyone’s responsibility.

  • Networks are becoming heterogeneous.

  • With heterogeneous networks, defense in depth becomes more difficult.

  • “Mind the gaps.”

  • All security must have a good user and developer experience.

Security is everyone’s responsibility, whether you’re ops, dev, QA. I think it’s everyone’s responsibility, including the business, as well.

We’re bringing a lot of cloud into our stack. All the good things like Kubernetes, like Nomad. But we’ve already seen this a few times at the conference. It’s making our network infrastructure quite heterogeneous. There’s a lot of things in the mix now.

Defense in depth is super important, but bringing in more things makes it harder to implement. I’m not going to cover security-scanning your applications, scanning infrastructure. I’m going to focus only on network communication, because I’ve only got 30 minutes today, but I will briefly touch on some of the things the industry thinks are really important with security.

One key takeaway is, “Mind the gaps.” I’ve seen a bunch of people using great stuff in Consul, using great stuff in things like Ambassador, NGINX, using things like CDNs, but leaving subtle gaps in the security.

If you’re trying to do TLS from end-user right through to service, there are quite a few points where you might terminate TLS and reestablish it. And it’s very easy with different people owning different bits of the stack—ops owning a CDN, say, or dev owning the service mesh —to have gaps if you’re not careful.

Lastly, all security must have good user experience, good developer experience. I’m a massive fan of the HashiCorp stack for this reason. I think the UX of things like Terraform and Consul make it super easy to do the right thing. And we need to really remind everyone on the team about this kind of thing.

I’m a product architect at Datawire, and as we are a small company, that basically means I do everything from presales, solution architecture, dev advocacy. My background is very much Java developer. I moved into ops about 5 years ago. I met Nic Jackson, now developer advocate at HashiCorp, at a company called in the UK. We were both working there, bringing in Consul, bringing in Terraform into the stack. I have remained friends with Nic since and he helped me put this talk together. We did an early version of it at Kubecon, in Barcelona, recently.

Most of the code snippets you see today are from Nic. So, massive hat tip to Nic. I’m going to put out some extra code next week, as well. Stay tuned for that.

Some scary security numbers

As Nic and I were preparing this talk, we found some quite scary numbers. We found that:

  • 214 records containing personal data are exploited every second.

  • The average cost of a breach is around $4 million. If you’re leveling up to breaching like 50 million records, the cost goes crazy, to $35 million. And this will be a game-changing event in your company.

And these things are real. Just this morning, I got an email from the Financial Times saying that British Airways are going to be fined up to £183 million in terms of GDPR. This is a real thing. Even in the US and other countries, people are modeling things like GDPR. Privacy and security are becoming serious. Governments will issue fines, and this can be a business-ending moment if we’re not careful.

And it’s only going to increase, these kinds of attacks. This data is a couple years old, but a 72% year-on-year increase. I think I read somewhere that cyber criminality is bigger business than the international drug trade now. Because it’s so much easier to get involved in cybercrime, so much harder to get caught than it is if you’re drug running.

People are after our stuff. State actors right down through opportunists are looking to hack our systems.

They’re quite scary, to be honest, the stats, but it’s a good tool to convince the entirety of your business that security is really quite important.

Bringing security along during modernization

IApp modernization is a gift and a curse. A lot of us, we’ve got our users accessing our systems. We’re bringing in some Kubernetes, we’re bringing in some Nomad, all this good stuff. But we’ve got these heritage systems, as I like to call them: systems that make the money for us as a business.

And the reality is we’ve got to look after all of this stuff. If you’re an old-ish business, you may even have things like mainframe. Or one classic example I saw under someone’s desk—a really important database that had been left ticking away for several years, and we need to network this into the stack, as well.

This makes it really hard to think about security.

Defense in depth

You really have to think on every level, whatever your role is in the organization. There are some fantastic books that I have learned so much from. In particular, Adam Shostack’s book Threat Modeling is the gold standard of thinking about security in your delivery workflow.

Some other books:

You need to think about things like hardening and scanning infrastructure and, also, very critically, because we often have polyglot stacks these days, is you need to scan your code, the dependencies, and the packaging—containers or servlets or whatever.

Shameless plug for a book I wrote last year, Continuous Delivery in Java, with my buddy Abraham Marín-Pérez. If you’re in the Java space, we wrote a whole chapter focusing on how to scan everything from code to packages, and with scanning dependencies along the way. This stuff is really not hard to do in modern build pipelines. I work a lot in Java, but I know all the other spaces—.NET, Go, Ruby, there are similar tools in there. We won’t cover much of that today.

We’re not only going to be looking at encryption at rest. Encryption at rest is really important, but you also need to encrypt data in transit). And I’m going to be looking a little bit at the principle of least privilege. This is really important in terms of authenticating users, but even now, authenticating services.

Least privilege

We’re breaking down our applications into many services, and we used to be all in the monolith, where everything can access everything. Now we’ve got the opportunity with microservices to be more restrictive on what can access what. Microservices aren’t for everyone. They definitely introduce quite a bit of pain, in my experience. But they do have a lot of advantages, and one of them is this ability to be able to isolate different services in terms of security.

Exploring end-to-end comms

You’ve got this stack or you’re working toward this kind of stack, your hybrid of new world and old world. Consul is fantastic as a distributive key-value store. I’ve been using it for many years doing this. It’s a great service discovery mechanism as well. And it spans across all the things. I’ve used Consul template a bunch on VMs, the Kubernetes integration. I’ll talk a little bit more about that in a minute. But that is first class. The Helm Chart is fantastic for deploying Consul. Really easy to hook up all these things.

You also need an edge gateway. Shameless plug, I work with Datawire. We created the open-source Ambassador API gateway. There are many other gateways out there. But you really need your gateway to be talking, once the users are making requests, to something like Consul. When the user makes that request, in particular in the Kubernetes world, everything is ephemeral. Things change dynamically. You want to know where to route that request to.

Even at the service level, you need to know where to route to. Once you’re going upstream you need to look at Consul, —where is this service located? And it can also be located on other subnets, other VPCs, maybe on some co-located infrastructure, these kinds of things.

The API gateway and the north-south traffic domain

Let’s look a little bit more at the API gateway, because I see a lot of confusion around what we call “north-south traffic.” Usually, we call ingress traffic north-south. North-south is coming into the system, whereas things like service meshes look at east-west, which is service to service.

Focusing for a moment on north-south ingress traffic. Fundamentally, the job of the edge proxy is to expose internal services transparently to end-users. You don’t want your user caring or knowing what is on the backend serving the request. The API gateway should expose routes, or it should expose gRPC APIs and the client simply makes a call into that.

In terms of security, it’s a great place, obviously, to do TLS termination. But there are things to think about.

Mind the Gap #1

If you’re on the edge, you need to do things like minimum protocols version. Because a common attack is to try and downgrade the TLS protocol, and versions 1.0 and 1.1 have known issues; 1.2 is the gold standard, or 1.3. But you need to think about these things.

It’s also a great place to do end-user auth. We do a whole bunch of integrations in Ambassador with things like Okta, Zero, Keycloak. There are many identity providers out there that offer fantastic ways to authenticate users with social logins or with integrations into Active Directory, and then, often, give you a token back that you can propagate down through the stack.

You do need to make sure your apps are capable of propagating that back down the stack, but you can apply scopes or roles after the authentication and do authorization at the service level, based on that token.

Mind the Gap #2

A key thing with security is that it’s not just about authN and authZ. It’s also about things like rate-limiting. A very common attack is to fuzz APIs or just constantly hammer them, DDoS them, looking for memory leaks, looking for issues. So you want to rate limit, maybe based on IP, based on country, a whole bunch of other things. This is very much part of your security at the edge.

A look at Ambassador

I’m going to walk through some open-source config with Ambassador, just to give you a flavor of what I’m talking about. We use CRDs to do config. Ambassador is Kubernetes-native, sort of born of that era. You just define some config in a CRD and a Kubernetes resource. We specify an API prefix, /api. I’m using Consul as a resolver, so it will look to Consul, look for the service, emojify-api-sidecar-proxy, and it will route the ingress traffic to that service.

I’m using TLS. I’ve got a secret with some integration and that will look at Consul to get the TLS certificate and all the traffic going from the edge, from Ambassador to the service. We’ll look more at how this works in a minute. All of that traffic will be encrypted, as well.

That’s great for developers. The beauty of doing things like, say, Kubernetes’ mapping is each dev team can work on their own mappings and they’re not all working on one centralized config file. So they can all do their own prefix configuration, their own rate-limiting, their own circuit breaking, a bunch of other time-outs.

But you can also centralize some things, for example, TLS. You want TLS termination to apply to all of the endpoints. If you’re an operator, in particular, you want to centralize sane defaults. You might want to have some global rate-limiting policies that people can override if they want, but you want the sane defaults to make sure the system is secure. This is a good place to do this kind of thing.

I’ve got an example of how to always upgrade the protocol to HTTPS. It’s just a Kubernetes secret with a certificate. And that’s the way we terminate TLS Ambassador.

Managing certs with Vault

One thing I would say is, “Friends do not let friends manually generate TLS certs.” I see this in big companies. I see this in small companies. I’m a massive fan of things like Vault, of course, but also cert-manager. If you’re in Kubernetes, this is a fantastic tool using the ACME protocol, using something like Let’s Encrypt, something like Vault on the backend, and it will automate the renewal of TLS certificates.

You should never have your users telling you that they’re getting a warning in their browser and the padlocks failing because you’ve got an expired certificate. And I’ve done this, so I’m a bit of a hypocrite, but I would strongly recommend you do this kind of automation. Whatever you use, let’s say Vault, cert-manager, a bunch of other great products out there, this stuff is really, really important.

We’ve got a bunch of integrations in Ambassador where you can use cert-manager to automatically update your TLS certificates and so forth.

Be careful with CDNs

Quick aside: As I said, this is all focused on end-to-end security, from user right through to service. And CDNs, or content delivery networks, are often in the mix.

Mind the Gap #3

Some folks treat CDNs as magical security wrapping. Put a CDN on it—boom, it’s done. Be very careful with that stuff. I’ve used Akamai, CloudFlare, bunch of things over the years. They’re all really good, but you need to make sure you’ve got the security configured correctly.

You need to make sure you have an origin certificate installed. CloudFlare, say, may be terminating TLS at the edge, but you want to make sure that the transport from the CDN to your origin is also TLS. CloudFlare makes it super easy to install an origin certificate on something like Ambassador and ensure that all traffic is encrypted.

There are also things like HSTS, or HTTP Strict Transport Security. It’s a new protocol or new standard for forcing everything to be HTTPS on the website. It will not allow HTTP traffic. You can do automatic upgrades from HTTP to HTTPS as well, and you can even do mutual auth with things like CloudFlare, so you can make sure that it’s only CloudFlare talking to your origin. Kind of like mutual TLSs as opposed to just TLS encryption. Very nice stuff.

Like I mentioned, people treat CDNs as a magical security wrapping. And there’s a great paper I bumped into. I think this is courtesy of Johnny Xmas. If you’re looking at white-hat hacking, Johnny Xmas has got some fantastic talks online. We interviewed him for InfoQ, as well. And there’s a whole bunch of academic research around how easy it is to bypass things like CloudFlare and other CSPs, or cloud-based security providers. This stuff is really important to think about. Make sure, even if you have got a CDN, that you hide your origin, you make sure all traffic going to the origin is encrypted, all this kind of good stuff.

Consul and the east-west traffic domain

Moving on now to more Consul’s domain, and east-west traffic, the service-to-service traffic. I was super excited when I heard last year that Consul was moving into the service mesh space. It’s a very popular topic. Istio has been generating a lot of buzz. But I think Consul has the second-mover advantage of watching what’s going on. And HashiCorp has fantastic design principles. I’m really liking the Consul approach to service meshes.

A service mesh is focused on exposing internal services to internal customers, internal consumers. We should not care where things are running, in an ideal world. We should make a request to an identity, and that should work fine. I should say, “I’ve got a web server, I make a request to the app tier, app tier makes a request to database.” We should be able to just use those principles and not worry too much about where these things are running. We need to make sure all of the traffic is secured and the endpoints are secured, but the service mesh will hide the location of the services.

If you can guarantee communication between your service and your proxy—you’re running everything on a loopback adapter—all communication is going via the proxy and, therefore, you can put the identity using something like an X509 certificate from that proxy. So it’s transparent to the app, but the proxy gives an identity that Consul can manage: web identity or database or app tier.

Once you’ve got identity, you can do TLS so you can secure transports in all these services. You’ll also want to look at things like access control lists (ACLs), who can do what on the Consul server. You don’t want just anyone being able to apply various intentions of who can speak to who. You also look at those kinds of things. Intentions are really interesting in and of themselves. Intentions allow us to use service identity to define what can speak to what. So web can speak to app tier, app tier can speak to database, but web can’t speak to database, for example.

The service mesh fits everywhere, and all the communication is going through it. It’s a great way to do things like observability and also enforce metadata as being propagated down through the stack by the app.

The service mesh doesn’t come without a cost. Service meshes are quite expensive, sometimes, to operate. To be fair, I haven’t had much experience with Consul yet, because it’s quite new, but we’ve worked with customers with Istio. Istio’s great in terms of functionality, but its operation can be quite complex and quite resource-heavy in some situations. They’re doing a lot of great work to make that easier. But you have to bear this in mind: Another layer in the stack clearly adds complexity and clearly has resource overhead. But there’s a lot of good things with it.

Exploring entering comms

I mentioned Envoy a few times. It is a great cloud-native proxy, something like an NGINX and HAProxy, I’ve been using it for many years. Envoy was born out of Lyft. Matt Klein and the creators behind it have done a lot of work in Amazon and Lyft and other places. They really understood how to make the proxy very relevant to the cloud.

I’m not saying these older proxies are bad, but they’re born of an era not of the cloud, and it can make configuring them and running them on cloud somewhat challenging sometimes.

The secret is Ambassador, an open-source project; it is a control plane onto Envoy. And it’s customized for north-south. We’ll take the config you write in the CIDs and Kubernetes, and transform them into Envoy config, because Envoy config is quite hard to manually write. It’s a JSON or YAML file, very understandable but really hard to create, so you want to automatically generate them. And then, same thing with Consul. Consul can deploy an Envoy proxy next to all your services.

Pushing config with Consul

In this case I’ve got a bunch of services. Consul will handle things like certificate generation and sending out certificates to each of the proxies. You can use Vault and all that good stuff on the backend, but Consul is the one pushing out all the configuration to these proxies, the TLS certs and so forth, and all communication to the services is now going via the proxies.

Again, if you’re running something like on the loopback adapter on localhost, no traffic leaves the instance or the ports without going first through something like Envoy. And when that happens we can do a bunch of interesting things in the proxy, such as apply spare security things and also do observations and so forth.

But Envoy is fantastic. Hat tip to Matt Klein and the Lyft team for creating this. And hat tip, as well, to HashiCorp for integrating it so nicely into the Consul stack.

I’m not going to do lots of Consul config stuff because I’m going to assume most of you in the room are familiar with Consul. If not, the docs are fantastic, anyway. But I do want to say, I’ve been using a bunch with Kubernetes of late. And I’ve said it once but I’ll say it again, the Helm Chart install for Consul is awesome. I was playing around with 0.81 and 0.82 a few days ago and it just works. Nice config. Secure out of the box now. Real good stuff.

Consul and Kubernetes play well together

Once you’ve deployed Consul into your Kubernetes cluster, it’s literally 3 or 4 lines of config to wire a service from Kubernetes into Consul. We wire in the Envoy proxy to our service using that one line, connect-inject. There’s a mutating webhook that is installed as part of the Helm install that will fire up an Envoy, do the bootstrap configuration, run it in a Docker container next to your service. Providing you bind your application to the localhost or the loopback adapter, and then make requests out via ports, you can define your upstreams. So, anything we do will go to the Consul service called emojify-facedetect.

Super easy to do the right thing. We can very easily connect, put our proxy into the service, specify the upstreams, and then we can just do localhost 8003 or localhost 8005, and Envoy and Consul will automatically handle not only all the routing associated with that, like the dynamic routing, but it will also do the identity and the TLS management, as well. Great stuff.

Mind the Gap #4

One thing I would say is, ”Mind the gaps.” There’s a bunch of places from end-user to service where you can accidentally forget to do encryption. As I’ve mentioned before, I do see a bunch of folks encrypting at the CDNs, so terminating TLS at the CDN, but then forgetting to encrypt the traffic from CDN through to origin. I see a bunch of folks not running a CDN but terminating TLS at the edge, as that’s ops’ responsibility, but then the dev team are managing the service mesh or some other thing and they’re not securing that initial hop. So from the edge, TLS terminates, hop to the first service, then TLS upstream is engaged, but that initial hop they’ve missed.

You definitely want to make sure that any hops, internally or across to VMs, are secured, as well. “Mind the gaps” is a nice reminder to really think about this stuff.

Identity and network segmentation

I mentioned before that this “Death Star” model of security is potentially a bit dangerous. It’s a good first start, but it’s a bit dangerous. If you’ve got a service that is vulnerable to remote code execution, people can get in, get some kind of shell perhaps into the system, then, depending on what kind of access they get, it can be game over if they can get root access on a box or a port. This is really hard to defend against, but we want to stop the opportunists. If they can manage to get some kind of access to your services, we want to limit the damage.

Obviously, there’s an infrastructure level of segmenting, and I’ll cover that in just a second. But we also want to secure traffic between the services. If someone does manage to get in, say due to a vulnerable Java dependency or a vulnerable node dependency, they can often start moving around in the system. That’s quite damaging. And they can lurk, as well, and be very opportunistic in what they find and what they steal.

We’ve dealt with this since networks were a thing, basically, by doing segmentation. You have frontend and backend. Anyone who’s done VPCs and Amazon files and Google, you’ll recognize their sort of virtualized concept of what used to be racking and stacking and using routers and various things and using firewalls.

In the more modern microservice-type world, we’re often running all our backend services on something like a Kubernetes or a Nomad. We do still want to say that A can only talk to B and C, and not D, but how do we do this? Particularly within dynamic environments. Things are coming up, they’re going down, IP addresses are changing on the pods in Kubernetes, all this stuff makes it really hard with the old approach of using ports and IP addresses to lock things down.

What you really want to move from here is the IP roles to intentions, like Service A can talk to Service B. And you also want to be able to identify components within the system. We’ve got the Ambassador component here. This is, like Service A, Service B, Service C. We really need to be able to identify the components within the system to enable the intentions to say, “Frontend can only talk to web or only talk to app.”

I showed you the Consul config earlier. The intentions part of Consul is super easy. You can write in the command-line interface, “Consul intentions creates deny all,” and that basically will stop all traffic using Consul and Consul Connect. And then you can do “Consul create allow” and selectively allow communications. Something like an ACL, if you’re familiar with that from Amazon. You can add in intentions on the top of the default deny and very securely define what can talk to what.

Service Mesh Interface (SMI)

If you’re a bit hipster, you can use something like Service Mesh Interface, the new standard announced with Microsoft and HashiCorp and a bunch of other folks—Buoyant’s Linkerd, the Istio people—only a month ago at Kubecon. So it’s a brand-new thing, and Nic, hipster that he is, was telling me all this good stuff about SMI. And he’s woven it into the demo.

Looking through I was a little bit skeptical up front. The idea with the Service Mesh Interface is to create a layer of abstraction over all the service meshes. You’re a little bit careful with the lowest common denominator, but the idea is that you can define certain configs with the SMI. It doesn’t matter whether you’re running Consul, Linkerd, Istio; you define it in the SMI spec and then it gets translated per your target. And Consul has got support for SMI. So, rather than writing, say, the intentions manually or using the Consul API, we can now use the more Kubernetes-native way of defining things.

Yes, it’s a little bit verbose, but it is quite new. I think the community is looking for input from you, in particular, on how to refine some of these ways of defining intentions and traffic rules, traffic policies. But, for me, it makes a lot of sense. Having done a bunch of work in Amazon, in particular in AWS, I define my traffic target, it’s a CRD within Kubernetes. It’s an entity defined within Kubernetes. I specify a route that I’m working with. This is a simple TCP route, but you can also define HTTP routes with different prefixes, so metrics here, API here, for example.

It’s a default deny all so you say, “Ambassador can talk to Emojify website.” And it uses service accounts in Kubernetes, and Consul does some very clever stuff with mapping service accounts to service names, for example. It’s really nice, and if we’re using the GitOps approach, we can put all this config in our pipeline, test it, continually roll it out. It’s much easier than trying to do CLI calls or something like that.

I was, at this point, going to do a demo. But in my jet-lagged state, I couldn’t get it working. The good news is that Nic has put up a fantastic GitHub repo with all the code we’re going to demo. Emojify: it gives the actual app stack; it gives all the Terraform; setting stuff up; all the Consul config; SMI demos.

Nic and I presented a very similar version at Kubecon, and our talk is online, and Nic even redid some of the demo stuff and put it up on YouTube. And, I promise, by next week, I’ll do an updated demo. If you go into the Ambassador Pro Reference Architecture on GitHub, there’s an early Terraformed example with GCP, using VMs and Kubernetes, which I’m working on, and I promise next week or, maybe the week after, very soon, I will put that up and record that and share my content on that, as well.

Security is everyone’s responsibility

On that note, I shall wrap up, and say as a conclusion: Security is everyone’s responsibility. Say it again. It doesn’t matter where you are. And you need to work together. I know we’ve got DevOps, I know we’ve got SRE. It’s all great stuff. But I do see some organizations just relabeling old departments as DevOps, relabeling them as SRE, and they’re still not communicating. We really need to make sure that security is everyone’s responsibility.

If you’re trying to do code scanning, get it into your pipeline early. If you’re trying to encrypt all your traffic, work together to make sure there are no gaps.

I mentioned that I think this app modernization trend is really good. We’re bringing in more things. We are moving things like Kubernetes, like Nomad. Great stuff. But we have to be respectful that the infrastructure is quite heterogeneous. And that makes it quite challenging to do security well. So using things like Consul, I think, is a really nice way of putting a layer of abstraction over the top of a lot of these things.

Defense in depth is vital. I’ve said it once and I’ll say it again. You need to think about a whole bunch of things in terms of security, but even at the communication level, you need to think from CDN to edge to service to data store. You want to encrypt the whole gamut there.

Do mind the gap. It’s very easy to forget or misconfigure things. Want to make sure security is enabled all down the stack.

And lastly, it’s really important to say that things like Consul make it easy to do the right thing. That’s part of the HashiCorp ethos, in some ways. Security is even more important, because we will bypass stuff that’s hard to do.

We need perhaps to think about the control planes for north-south and east-west. They are subtly different, the requirements at the moment. Ambassador focuses on north-south, and other tools do the same thing, in that space. And Consul focuses very much on the east-west traffic, as does Istio, as ds other things. But do recognize that security needs to be thought about through all those things.

Some references

If you wanted to go and play with some of this stuff, you don’t have to spin anything up. You can play in the browser. It’s fantastic. Great way to learn Consul. Great way to learn a bunch of stuff from HashiCorp, actually, so definitely check that out.

Thank you for your time.

More resources like this one