Monday, August 16, 2021

Explorations of a wannabe security programmer

 Right now I am in that state where I am trying to make sense of different security building blocks, and feeling for the true use cases they help solve. This has been an interesting journey because it builds understanding of problems as well as perspective. The uber-perspective is that security is never really a fully-solved problem. Getting the basic interactions and workflows right is merely the beginning. There are many scenarios and edge cases to take care of when dealing with real production deployments, and there are a ton of concerns around hardening the security of such a deployment. It's a game of probabilities, and therefore of prioritization.

HashiCorp Vault

HashiCorp Vault has been at the top of my list of technologies to understand for a while now. That's not only because it seems like a particularly good component to build a cloud-agnostic architecture around, which it is. It's also because of how well it helps address security concerns that often do not figure in the architect's priority list till the security team comes back and either vetoes the release or does some hard-talking to extract commitment for a vulnerability fix patch.

What I figured while trying to learn Vault is that it isn't straightforward, and while there is plenty of documentation, what is lacking is a good cohesive introduction that shows the big picture well. I thought I'd jot down, still rather tersely, my understanding of what Vault essentially is and does.

HashiCorp Vault is a service that you run in your deployment, and which stores and protects your secrets from unauthorized access, credential theft, and proliferation of secrets.

  1. It stores secrets securely, ensuring secrets are always encrypted, whether at rest or in transit.
  2. It allows only authorized access to these secrets.
  3. Easy access management through policies
  4. Integrates with a wide number of applications, databases, continuous integration systems, container orchestrators, etc.
  5. Recommends ways to secure deployments from the outset.
The last point, which HashiCorp calls the Secure Introduction problem is significant. Essentially, it tries to answer the following question.

When a new component (a service, container, VM, etc.) is introduced into a system, how do existing components authenticate it? In essence, it is the problem of establishing trust where none exists to begin with, and needless to say, it relies on some best practices rather than any innovative technique to get the job done.

How does Vault work

Vault works through a set of abstractions for creation, retrieval and management of secrets. Here are the key abstractions.
  1. Storage engine: The storage backend used to store the secrets securely. This could be local disk, a cloud-based key management system such as Azure Vault or AWS KMS, HashiCorp Consul, etcd, etc. This is part of the server start-up configuration.
  2. Secrets engine: The secrets engine defines the type of secret that is protected, as well as the kind of metadata associated with a secret, and the operations that can be performed on it. For example, these could be static key-value pairs, database credentials, etc. Secrets and their associated metadata can be stored and retrieved using a path-based naming scheme of the form: <engine>/<path>/<secret-id>.
  3. Sealing and unsealing: When a Vault server bootstraps, it does so in a sealed mode (except when you run it in dev mode which is obviously only for development). Sealed mode means that the Vault server has no way to retrieve and decrypt keys stored in its storage backend. It must be unsealed to begin operations. Vault uses a master key to unseal itself and generates this key during initial configuration. But it goes a step ahead and shards that key into N participant keys, any M (<=N) of which are required to unseal it. The idea is that these N keys would be with N different individual admins, M of whom must enter the key at the time of unsealing. Also, the Vault can be sealed back by using just any one of these N keys. The algorithm to shard the master key into M keys is called Shamir's Secret Sharing algorithm.
  4. Authentication backends: Vault integrates with several authentication backends, including a plan user/pass backend, LDAP and ActiveDirectory, and many others. This is used for authenticating clients of Vault that want to access secrets protected by Vault.
  5. Tokens: Tokens are strings that are used to access the actual credentials (such as usernames and passwords). They have limited validity (TTL) and limited renewability. Each token may have any number of policies associated with them which determine what can be accessed using the token. Depending on the secrets engine, the actual credentials are generated only when a user requests them using a token, and are revoked when the token expires.
    1. Root tokens:
    2. Token hierarchies:
    3. Cubbyhole tokens: Cubbyhole tokens are special key-value pairs in which there is a wrapping token and a wrapped token. The wrapping token can only be used twice: first to store the wrapped token, and then to retrieve it. On retrieval, the wrapping token expires and the wrapped token is retrieved. This is useful for generating one time use passwords that need to be securely retrieved.
  6. Policies: A policy consists of a secret path and associated privileges (such as read, write, create, update, get, list, etc.). A token can be used to access secrets from all paths that associated policies grant access to.
This is blog post is a work in progress. Expect more details to be added.


Read more!

Saturday, July 31, 2021

The struggles of a wannabe security programmer

 Yeah, I'm impressed that I have been able to sort of grok what goes by the name OAuth2.0 and its bastard child OpenID Connect or OIDC. And can at least make sense of a conversation on SAML. That in itself is no mean feat, frankly. YouTube was quite helpful at that, which is a rarity in my case. But what to do beyond that? I want to try this stuff with my own hands and I am frankly out of depth here. I have a laptop or two where I can set stuff up and tinker with things. How do I get started?

Maybe auth0 is the place? Or maybe ory/hydra + ory/kratos? Could it be dex? But they can't write a paragraph without talking about kubernetes! Maybe the do-it-all grand daddy of the trade - Shibboleth? Or the really cool Authelia? How about caddy-auth-portal.

There lies the problem. There's plenty to grok on the topic, all of it in the open source. But almost everything is so hostile to a beginner that unless you're ready to read code and try all kinds of google searches, and YouTube videos and piece together a picture for yourself, nothing will let you get to the heart of it and build an understanding by doing stuff in an isolated environment with minimal dependencies. Yeah, I hate it when you start talking about how to run this thing with docker or docker-compose without bothering to write a paragraph about the architecture and dependencies. But such is true open source these days. If you aren't doing this, you aren't doing it right I guess. The thing is, good man pages and documentation go a long, long way to building understanding. It seems that they are also incredibly hard, because making your writing useful (as opposed to merely legible) requires empathy.

Yeah, ranting! But ok, what are my key takeaways on OAuth2.0 and OIDC?

  1. It's complicated. You can still grok it.
  2. Ultimately, it is a mechanism for granting a time-bound token (the access token) to an application (the client app) to access protected information (the resource).
    1. In general, the token can be used to query a defined set of data from a protected service (called the resource server). This means the token is only good for accessing data it was supposed to be used for and not have an unrestrained access on the resource server.
    2. In the case of OIDC, that protected information is actually information about a user who has been authenticated by the process. Basic info is available as a second token, a JWT token called the ID token. For additional info, you 
  3. How does it work? Through a system of redirects.
    1. The center of all action is the client app. If it is unable to figure out the access tokens to use, it redirects to an authorization server, which protects access to the resource server.
    2. Now obviously, it is the user (called the resource owner) who can authorize the request. They do so by:
      1. Authenticating themselves at the authorization server.
      2. Consenting to allowing access to specific resources (referred to as scope) on the resource server.
    3. When the above process completes, the authorization server redirects the request back to the client app with an authorization key and some other identifiers. This authorization key allows the client to initiate interaction with the resource server, on behalf of the resource owner.
    4. You'd have thought that this is the end of the story. But not really. This key just allows the client app to initiate communication, but to continue it, the client app must get an access token in exchange of this auth key. This is where the details we so far overlooked start to matter.
      1. How does the auth server know which client app URL to redirect to?
        Actually, each client app must individually pre-register with the authorization server. When they do this, they get a client identifier, and a client secret. These are sent as part of the initial redirect to the authorization server.
      2. Why does the auth server issue an access token to the client app only after getting the authorization key from the client?
        The authorization server keeps track of each authorization key it issues and the client it issues it for. Assuming that the client is not compromised, it only trusts a request carrying this authorization server which also carries the correct client id and client secret. That's when it responds directly to the client with the more durable access token.
There is no public key exchange / certificate signing that happens between the resource server and the client app so that they could trust each other via mTLS. Instead the trust is mediated by the resource owner who gives consent to the interaction. But the resource server isn't going to trust any arbitrary caller just on the basis of the resource owner's consent. That's why:
  1. The client app must independently register itself with the authorization server, and obtain its identifiers.
  2. When the resource owner consents to the request from the client app, the authorization server verifies that this is a client they know before issuing the initial authorization code. This wouldn't be possible without step 1.
  3. And finally, the authorization key it sends back to the client app via redirection must come back to it from the same client before it can allow the client to begin a trusted communication with the resource server.
Obvious questions would be:
  1. What does the client do as part of the registration with the authorization server?
  2. How does the resource owner validate the access token in each request from a client?
  3. How does the resource server authorize the request carrying the access token?
I have some idea about these but I will add them here when I know better.

There are actually several flows defined by OAuth 2.0 and the one described above is the Authorization Code flow or simply code flow. There is also an implicit flow which is less secure - which sends the access token to the client app directly instead of sending a temporary auth code first. The OIDC flow likewise is different.

PS. By the way, I forgot to mention that I do have something of an idea about how to try this hands-on. I will go with dex, and try setting up an LDAP authentication server on Linux, then point dex to it. At least that will be a start.


Read more!

Monday, May 24, 2021

Out of touch with C++, and other new stuff

MongoDB is quite cool. Apart from being written in C++, it's a document DB (one that can store and index JSON / JSON-like content) in a distributed database that supports sharding, replication, and eventual consistency. You can run it as a single server too, which is useful for writing application code that connects to it.

  1. The server is called mongod.
  2. There is a REPL client / interpreter called mongo which also happens to be a full-fledged JavaScript interpreter.
  3. A single Mongo instance can host multiple databases, each stored in its own file.
  4. Each database can have multiple collections. analogous to tables in an RDBMS.
  5. Each collection has zero or more documents, corresponding to rows in RDBMS but the similarities are faint.
    1. A document is a JSON or JSON-like content. JSON-like refers to the fact that there are extensions to the JSON-format supported - with value types like Date, binary, etc. being supported.
    2. Every document has a unique identifier - the _id attribute, which must be unique in a collection. It's default type is ObjectId, which is a 12-byte integer, but can be any type. The 12-byte integer is partitioned from left to right as "4[epoch seconds]|3[hostname hash]|2[pid]|3[incr num]".

The main meat of the topics are perhaps in dealing with distributed data - sharding, indexing, consistency, and all the tools used to implement these. There are also specifics like expression languages for queries, as well as client libraries. There are also many specifics about working using the mongo REPL. Last, but not nearly the least, is the topic of building applications using Mongo - a topic that influences how application data is expressed via Mongo, and consumed from it. More on that in another post.

C++ has been an old love affair, a fact that alienates me from a lot of well-meaning programmers at the outset, and endears me to a few. But truth be told I have not written a lot of C++ over the past few years and have grown my own contrarian opinions about the usefulness of many recent additions to the language. I didn't get a chance to work with fold expressions earlier but looked at it recently.

To me fold expressions appear to be a syntactic convenience for unrolling loops involving parameter packs without explicitly writing the templates and their specialization needed before. The code does become shorter, but does it really become more expressive? I don't know - I think it becomes a little cryptic / terse because the syntax doesn't intuitively express what's happening. You have to know and get used to it, like with parameter pack expansions involving function and template expressions.

I don't have much to argue on the matter. The C++ folks can always shut me up by saying that this is a tool for library writers that people like me can ignore. Maybe they are right. But I do wonder, why library writers have to put up with cryptic syntax. Isn't simplicity of value to them?


Read more!