MatrixAI / Polykey

Polykey Core Library - Open Source Decentralized Secret Sharing System for Zero Trust Delegation of Authority
https://polykey.com
GNU General Public License v3.0
29 stars 4 forks source link

Smart Tokens #385

Open CMCDragonkai opened 2 years ago

CMCDragonkai commented 2 years ago

What is your research hypothesis/question?

Split off from #166 so that this issue can focus on smart token research.

In relation to blockchain identities #352 and the DID work https://www.w3.org/TR/did-core/ in W3C, I want to point out that the oauth2 protocol and associated openid connect protocol is also related to this overall concept of "federated identity" https://en.wikipedia.org/wiki/Federated_identity.

Additional details: https://medium.com/amber-group/decentralized-identity-passport-to-web3-d3373479268a

Our gestalt system #190, represents a decentralised way of connecting up identity systems.

Now we can research and figure out how to position Polykey relative to all these different identity systems and protocols are being developed currently. But there's something here related to our original idea of "secrets as capabilities".

Recently I discovered that Gitlab CI/CD system supports using OIDC connect protocol as a way to bootstrap trust between the gitlab runner, the CI/CD job, and a secrets provider (like hashicorp vault or aws STS #71), and then allows the CI/CD job to request secrets that are scoped to the job (such as accessing some AWS S3 resource).

Documented:

This is basically an implementation of the principle of least privilege. Consider that the CI/CD job is being "privilege separated" by not being given "ambient authority" due to environment variables, and it is "privilege bracketed" because its secret-capabilities are only useful for that specific job, and once the job is done, it experiences "privilege revocation" either by having the token expire, cryptographically-one-time-use through a nonce, or through some state change in the resource controller.

AWS recommends against storing "long-term credentials", time expiry is a naive way of implementing privilege bracketing, since it is technically only bracketed by the time-factor/dimension and the "space dimension" by where it is given.

One of the useful things here is that the secret provider is able to change or revocate the secret after it is given to the user of that secret. This is basically a proxy-capability, which is how capsec theorises how to revocate capabilities.

This basically validates the idea that capabilities used in a distributed network context ultimately requires a "serialisation" format. And the serialisation of a capability is ultimately some sort of secret token. Using smart cryptographic logic, the embedding of "logic" into the token itself via JWT (structured token), you're able to build in "smarts" into the token that can function as a "capability". You might as well call this a "smart token" as opposed to dumb tokens where they are just a random identifier that is checked for equality in some session database.

This is very exciting, we are on the cusp of building this into Polykey. But it does require us to first figure out the trust bootstrap (gestalt system) and how it integrates into decentralised identifiers and OIDC identity federation, and then making usage of structured smart tokens (like JWTs) to enable logic on the tokens.

Now back to the topic of OIDC identity provider. AWS describes their "web identity federation" as:

IAM OIDC identity providers are entities in IAM that describe an external identity provider (IdP) service that supports the OpenID Connect (OIDC) standard, such as Google or Salesforce. You use an IAM OIDC identity provider when you want to establish trust between an OIDC-compatible IdP and your AWS account. This is useful when creating a mobile app or web application that requires access to AWS resources, but you don't want to create custom sign-in code or manage your own user identities.

This could describe any existing identity system that wants to allow external systems to interact with identities. Even things like "login with GitHub" is basically allowing a third party system to interact with identities on GitHub, and delegate the responsibility of managing identities to GitHub. But the depth of what it means to interact of identities goes deeper than just SSO. And this is what PK addresses.

And to be clear, Open ID Connect is OAuth 2.0 with extra features. So it's basically saying AWS supports OAuth2, login with AWS, and then use AWS resources in third party apps, but you can also do this with software agents, it doesn't have to be human-people.


Here's an interesting way of explaining this whole idea of "smart tokens". It was my journey through this.

Many years ago we start with CapSec - capability based security. It started some controversy and criticisms came in.

Then came the Capability Myths Demolished paper: https://blog.acolyer.org/2016/02/16/capability-myths-demolished/. It criticised the criticism. Also see: https://news.ycombinator.com/item?id=22753464. The paper itself is quite readable.

Then came c2 wiki: https://wiki.c2.com/?CapabilitySecurityModel.

In it, it explained that the decentralized world of the internet ultimately lacks a "central authority" (i.e. the kernel in a operating system) that forges and is the origin of the capabilities http://wiki.c2.com/?PowerBox, and thus one must be transferring "tokens" as reified capabilities that can be sent between decentralized programs.

The problem is that our tokens are just dumb strings that have to be matched for equality in some database. They didn't really satisfy alot of the cool things you can do in a capability operating system. And you could say they were easy to understand, and so everybody ended up creating their own ad-hoc session system without fully understanding all the implications and what it could be capable of.

So slowly we realised that these dumb tokens can become smarter by embedding information into the token. Like the HMAC protocol.

Further development led to the "macaroon" idea: https://github.com/nitram509/macaroons.js. A cookie.

Today we can instead talk about JWT, and I believe JWT has taken over. https://neilmadden.blog/2020/07/29/least-privilege-with-less-effort-macaroon-access-tokens-in-am-7-0/

However how JWTs can be used is still a point of R&D. And how JWTs could be used as "smart tokens" that realise the ideals of capsec across decentralised services is still being developed. I believe we can rebrand capabilities as "smart tokens" and it would have the cultural cachet of "smart contracts".

It'd be interesting to see how these smart tokens can be computed against, and how these smart tokens enable ABAC, and most importantly revocability which requires indirection (https://news.ycombinator.com/item?id=22755068).


Here's a real world example of this problem.

Both GitHub and GitLab support webhooks. Webhooks are a great way of allowing web-services to provide "push" based configuration mechanism.

Right now we have GitLab mirrors pulling GitHub, and it does it by polling GitHub. One of the advantages of push based configuration is the ability to avoid polling and to have minimal delay so that events arrive faster. A sort of "best-effort delivery" and reliable delivery.

So to make GitHub push to GitLab, we can configure a webhook on GitHub.

This requires GitLab to have an API that supports a trigger to pull on a project mirror.

This API call is here: https://docs.gitlab.com/ee/api/projects.html#start-the-pull-mirroring-process-for-a-project

GitHub has a great webhook panel for debugging webhooks.

image

But the problem now is setting secrets.

Apparently there is standard for secret passing on webhooks defined by the WebSub protocol, but not all providers support it. At any case, Gitlab suggests using the ?private_token=... query parameter.

The resulting web hook looks like:

https://gitlab.com/api/v4/projects/<PROJECTID>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>

Now you need an access token.

The problem is that this token creation has to be done on GitLab, and during the creation of the token, you need to grant it privileges, obviously we don't want to give GitHub ENTIRE access to our API.

  1. The docs don't explain what kind of privileges the call requires.
  2. Upon trial and error it is revealed that the token must be at Maintainer level, and you need the full api scope

image

This privilege requirement is TOO MUCH. The token isn't really safe, it's just sitting in plain text in the webhook UI settings of the project. The token is at maintainer level thus being capable of deleting projects... etc, and finally it's the entire api, rather than limited to a specific API call.

The ambient authority of this token is too much for something that creates a minor benefit (making our mirrors faster), and for something that is handled this insecurely.

Thus making this integration too "risky" to implement.

Therefore having "smart tokens" would reduce the marginal risk of implementing these sorts of things so we can benefit from more secure integrations. One could argue that integrations today are limited by the inflexibility of privilege passing systems.

Review existing ideas, literature and prior work

Research conclusion

Sub-Issues & Sub-PRs created

  1. ...
  2. ...
  3. ...
CMCDragonkai commented 2 years ago

On the topic of smart tokens.

JWT today is the most prevalent use of smart/structured tokens. I want to think of them as serialised capabilities.

The most prominent argument against JWT is that it makes it hard to revoke. And if you add in ways to revoke JWTs, then it is no different from just using session tokens that we have been doing.

But I argue that JWTs are useful regardless of whether you have a way to revoke them or not. I will point to revocable capabilities in capabilities myths demolished paper - "The Irrevocability Myth". The basic idea is provide a capability to a proxy, and then control that proxy if you want to revoke the capability.

image

The extra level of indirection does impact performance. But this is no different from the performance of using session tokens as we have been doing. The paper even addresses how we basically try to make session tokens faster... through caching indirect references!

Karger addresses the concern that “indirection through a large number of revoker capabilities could adversely affect system performance” by suggesting that “a properly designed translation buffer, however, could cache the result of the capability indirections and make most references go quickly” [13, p. 111].

Therefore transitioning to using of JWTs is not about performance. The performance is either the same or worse than using traditional session tokens. It's about power and flexibility. It expands the capabilities of secure systems to be more flexible and dynamic beyond monolithic ACLs (no matter whether they are MAC or DAC).

Capabilities are meant to also encode the address to the resource it enables access to. To make this possible, we can also think of JWTs like CIDs (content identifiers). The more prevalent deployment of this idea is the usage of one-time URLs or secret URLs like how github secret gists are done.

Usability wise, I would think that embedding the address within the JWT is not enough. Nothing knows how to interpret it, but if we take a page CID, all we would need to do is add a protocol and make a URL. Thus cap://<JWT> becomes a URL. At this point, it is possible to register applications that understand cap URLs and can make them "navigable" as they should be.

Of course whether a cap URL is useful or not depends on the agent opening the URL. If it is a browser, the expectation is that it supposed to load a web page.

But as these capabilities/JWTs are intended to be shared among machines, it makes sense that we're talking about agents beyond just web browsers, and these agents encompass our microservice applications that have to make use of JWTs to access third party services.

We are just in the beginning of JWT adoption. Right now people are still using JWTs like they used to use API keys. But eventually we will realise that JWTs are capabilities. And if they are capabilities, we move from treating them like pets to treating them like cattle. We just need the right mental model, libraries/tools, software solutions that can manipulate JWTs properly and we will soon have a "Web of Capabilities".

And in this new world, we could imagine PK as a powerbox.

CMCDragonkai commented 2 years ago

There's a connection between OOP, object capability languages (https://en.wikipedia.org/wiki/Object-capability_model), and the adoption of JWTs as capabilities in this new web3 world. When thinking about OOP, forget about Java, C++ class inheritance OOP, go back to smalltalk OOP. Where it's all about objects, late binding, and messages. The common idea between object capability languages and functional programming type theory community is "correct by construction". Where OCL people want programs that enforce POPL not from outside-in, like putting untrusted programs in faraday cages (containerisation, virtualisation, isolated-vms, WASM... etc), but from inside-out, where POPL is embedded from the lowest level constructs to high level constructs. With this in mind, JWT as capabilities means that JWT scopes or permissions or grants should be structured to include both the address and the permission. Consider the OOP expression o.abc(param). The o is the target, the abc is the method and param is the extra data. If we compare this it issues:write, we can say the left side of the colon should be a hierarchical identifier of the object, the object path, the right side should be the method that is abc. The public methods of an object are the permissions. And the only way to call another object is to have the capability to do so. Extending this to web-systems which are fundamentally decentralised, each web entity can only call another web entity if they have the capability to do so, and this is structured as a JWT that has the object path and the permission. Imagine URL and method like documents/1:GET. I wonder how this would apply to fully public methods, or methods that don't require any authentication. More research on object capability models and JWT structure is required. Once this is all possible we end up having a consistent way of modelling and analysing and auditing authentication/authorisation across the entire web.

I think fundamentally PK can provide a new programming model, not a new programming language, but by having a local PK node available (side-car style), we can provide the ability to manipulate JWT capabilities like first-class concepts in programming languages (might need to provide SDKs like pulumi though). They are are still a decentralised smart-token, like how smart-contracts are wielded, they don't apply to the program's own resources, but resources across the web.

CMCDragonkai commented 2 years ago

Another issue involving POLA. Software supply chain security. POLA is violated by downloading software, and running remote code (trusted but unverified), then giving them ambient authority such as environment variables injected into CI/CD pipelines, and where those pipelines download software from the internet to execute. Namespacing isn't sufficient here, because parent process environment is inherited by child process. If tokens are capabilities, and these tokens are inherited by the child processes transitively, then that's not POLA, unless parent processes tightly control what child processes inherit.

This is the last time delivery mechanism problem, but it can be helped by changing things in 2 ways. Making capabilities only useful for the designated target, and changing the delivery mechanism of capabilities from env variables which are inherited automatically and thus insecure, to passing as parameters (and in particular parameter files), or inverting the control, where target processes ask for capabilities (but this ends up with a secret 0 problem).

See examples:

CMCDragonkai commented 2 years ago

Natural progression to smarter tokens even for the people to machine: https://tidbits.com/2022/06/27/why-passkeys-will-be-simpler-and-more-secure-than-passwords/.

CMCDragonkai commented 2 years ago

Another issue involving POLA. Software supply chain security. POLA is violated by downloading software, and running remote code (trusted but unverified), then giving them ambient authority such as environment variables injected into CI/CD pipelines, and where those pipelines download software from the internet to execute. Namespacing isn't sufficient here, because parent process environment is inherited by child process. If tokens are capabilities, and these tokens are inherited by the child processes transitively, then that's not POLA, unless parent processes tightly control what child processes inherit.

This is the last time delivery mechanism problem, but it can be helped by changing things in 2 ways. Making capabilities only useful for the designated target, and changing the delivery mechanism of capabilities from env variables which are inherited automatically and thus insecure, to passing as parameters (and in particular parameter files), or inverting the control, where target processes ask for capabilities (but this ends up with a secret 0 problem).

See examples:

Based on this discussion https://github.com/containers/skopeo/issues/434#issuecomment-494631881 it appears:

while /proc/$pid/environ does allow other processes to read env vars, it's limited to processes that are allowed to ptrace the target process, whereas /proc/$pid/cmdline is readable by any other process by any user. So storing something in /proc/$pid/environ is about as secure as any file on disk secured via standard file permissions.

This explains why it's possible to set file variables in the gitlab interface:

file

CMCDragonkai commented 2 years ago

Something to be aware of is that environment variables are also scoped in gitlab, so we can place variables for jobs that have a defined scope. While this means you can create new env variables for different scopes, there's still a problem of capability tokens not being composable. If 2 programs both are using SOME_SECRET as an env variable to authenticate to things, then you end up needing to provide a SOME_SECRET capability that do what both programs needs to do. For example our aws and nix commands both want to use AWS_... env variables. But nix is for reading/writing s3 cache of nixpkgs, and aws for controlling ecs and ecr. The only solution for now was to create a key that can do both, whereas it would be more legitimate to create purpose-specific tokens and inject them into relevant programs where possible. This is classic namespace conflict. One solution is capability union (or composition of capabilities, which is possible with structural tokens), another solution is more POLA, so that one injects one token into one place, and another injects another token. This would often take place with some sort of aliasing, or a more fine-grained approach compared to env variables which is basically a form of ambient authority mixed with global variable pollution.

CMCDragonkai commented 2 years ago

In other news, regarding the security of chocolatey packages. Right now chocolatey is using packages provided by the chocolatey community. In particular the bill of materials include nodejs and python, although extra packages may be needed in the future. In that sense, its no greater or lesser secure than npm packages and nixpkgs. All rely on the community. Officially they recommend hosting your own packages and internalizing them to avoid network access, especially given that packages are not "pinned" in chocolatey unlike nixpkgs (and part of the reason why we like nixpkgs).

But from a security perspective no matter what you're always going to be running trusted (ideally) but unverified code. This is not ideal, all signatures can do is reifying the trust chain, and that ultimately results in a chain of liability. But this is a ex post-facto security technique. Regardless of the trust chain (https://www.chainguard.dev/), a vulnerability means the damage is already done. Preventing damage ahead of time requires more than just trust. And this leads to Principle of least privilege, which is enforced through one of 2 ways:

Most security attempts are done through the first technique. Some form of isolation, whether by virtual machines, containerisation, vm isolation, network isolation, and even environment variable filtering with the env command or namespaces, and even the above technique of using environment scopes. The second technique is not practical due to the legacy of our software architecture in the industry.

The fundamental problem with technique one, is that everything starts as open, and are we trying to selectively trying to close things, this is privacy as an after-thought. This is doomed to failure, because it is fundamentally not scalable. The fundamental problem with technique two, is that it makes interoperability something that requires forethought, this is privacy by default.

CMCDragonkai commented 1 year ago

Regarding OCLs and derivative/proxy capabilities see Cloudflare Zero Trust. I've signed up already for it. And this makes sense as the proxy resource, that you can setup access control for. PK would then be a decentralised version of that. It would need to have sufficient programmability to be able to derive secret capability... and it does seem like it. This is quite a huge change from VPN network boundaries, it's designed to integrate into SSO and directly allow access to internal systems. It's a bit complicated, and seems to have some flexibility, but I think the onboarding process of tailscale is superior for creating zero-trust networks between users of tailscale. But due to working with HTTP instead of VPN, it is thus more technologically more portable.

CMCDragonkai commented 1 year ago

472 goes into separating KeyManager into KeyRing, CertificateManager, and KeyManager. The remaining KeyManager duties are only useful in the context of this new smart token concept in terms of managing keys for signing/verification/encryption/decryption for JOSE, PASETO/PASERK, or biscuit tokens. See: https://fly.io/blog/api-tokens-a-tedious-survey/