GithubHelp home page GithubHelp logo

banzaicloud / jwt-to-rbac Goto Github PK

View Code? Open in Web Editor NEW
112.0 11.0 13.0 297 KB

JWT-to-RBAC lets you automatically generate RBAC resources based on JWT tokens

License: Apache License 2.0

Dockerfile 0.62% Makefile 6.79% Go 89.55% Shell 1.52% Mustache 1.52%
kubernetes rbac jwt dex authn authz

jwt-to-rbac's Introduction

CircleCI Go Report Card Docker Automated build

JWT-to-RBAC

JWT-to-RBAC lets you automatically generate RBAC resources based on JWT token.

Context

For authentication we use Dex with the LDAP and GitHub connectors. The users in LDAP have group memberships, GitHub users can be members of a team in an organization and Dex issues a JWT token containing these memberships. The JWT-to-RBAC project can create ServiceAccount, ClusterRoles and ClusterroleBindings based on JWT tokens. When we create a new ServiceAccount K8s automatically generates a service account token.

For more information and context please read the Provider agnostic authentication and authorization in Kubernetes post.

JWT-to-RBAC is a core part of Banzai Cloud Pipeline, a Cloud Native application and devops platform that natively supports multi- and hybrid-cloud deployments with multiple authentication backends. Check out the developer beta:

Requirements:

There are some pre-requirements to kick this of for your own testing.

  • Configured Dex server as OIDC provider which issues JWT tokens. If you want to issue tokens with Dex you have to configure it with LDAP connector. You can use the Banzai Cloud Dex chart.
  • GitHub account assigned for an organization or configured LDAP server - you can use the openldap Docker image
  • Authentication application which uses Dex as an OpenID connector (in our case is Pipeline.

Dex acts as a shim between a client app and the upstream identity provider. The client only needs to understand OpenID Connect to query Dex.

The issued ID tokens must contain the following claims:

  • name: string
  • email: string
  • email_verified: bool
  • groups: list of strings
  • federated_claims: object

federated_claims must contain:

  • connector_id: string (github/ldal/local)
  • user_id: string

The whole process is broken down to two main parts:

  • Dex (OIDC) auth flow
  • jwt-to-rbac ServiceAccount creation flow

Dex authentication flow:

  1. User visits Authentication App.
  2. Authentication App redirects user to Dex with an OAuth2 request.
  3. Dex determines user's identity.
  4. Dex redirects user to Authentication App with a code.
  5. Authentication App exchanges code with Dex for an ID token.

jwt-to-rbac Flow:

  1. Authentication App has ID token (JWT)
  2. POST ID token to jwt-to-rbac App
  3. jwt-to-rbac validates ID token with Dex or other OIDC prvider
  4. jwt-to-rbac extracts username, groups and so on from the token
  5. jwt-to-rbac calls API server to crate ServiceAccount, ClusterRoles and ClusterRoleBindings
  6. jwt-to-rbac get service account token and sends it to Authentication App
  7. Authentication App sends back the service account token to User
  8. User authenticate on K8s using service account token

The ID token issued by Dex has a following content:

{
  "iss": "http://dex/dex",
  "sub": "CiNjbj1qYW5lLG91PVBlb3BsZSxkYz1leGFtcGxlLGRjPW9yZxIEbGRhcA",
  "aud": "example-app",
  "exp": 1549661603,
  "iat": 1549575203,
  "at_hash": "_L5EkeNocRsG7iuUG-pPpQ",
  "email": "[email protected]",
  "email_verified": true,
  "groups": [
    "admins",
    "developers"
  ],
  "name": "jane",
  "federated_claims": {
    "connector_id": "ldap",
    "user_id": "cn=jane,ou=People,dc=example,dc=org"
  }
}

After jwt-to-rbac extracts the information from the token, creates ServiceAccount and ClusterRoleBinding using one of the default K8s ClusterRole as roleRef or generate one defined in configuration if it does't exist.

Default K8s ClusterRoles used by jwt-to-rbac

The JWT-to-RBAC dos not create a new ClusterRole in every case; for example if a user is a member of admin group, it doesn't create this ClusterRole because K8s has already one by default.

Default ClusterRole Description
cluster-admin Allows super-user access to perform any action on any resource.
admin Allows admin access, intended to be granted within a namespace using a RoleBinding.
edit Allows read/write access to most objects in a namespace.
view Allows read-only access to see most objects in a namespace.

jwt-to-rbac crate custom ClusterRole defined in config

In most of the cases there are different LDAP groups, so custom groups can be configured with custom rules.

[[rbachandler.customGroups]]
groupName = "developers"
[[rbachandler.customGroups.customRules]]
verbs = [
  "get",
  "list"
]
resources = [
  "deployments",
  "replicasets",
  "pods"
]
apiGroups = [
  "",
  "extensions",
  "apps"
]

define GitHub custom roles in config

[[rbachandler.customGroups]]
groupName = "githubOrg-githubTeam"
[[rbachandler.customGroups.customRules]]
verbs = [
  "get",
  "list"
]
resources = [
  "deployments",
  "replicasets",
  "pods"
]
apiGroups = [
  "",
  "extensions",
  "apps"
]

or specify GitHub organization as default org

[rbachandler]
githubOrg = "github_organization"
[[rbachandler.customGroups]]
groupName = "githubTeam"
[[rbachandler.customGroups.customRules]]
verbs = [
  "get",
  "list"
]
resources = [
  "deployments",
  "replicasets",
  "pods"
]
apiGroups = [
  "",
  "extensions",
  "apps"
]

Example configuration in yaml using default GitHub org

issued jwt:

{
  "iss": "http://dex/dex",
  "sub": "xxxxxxxxxxxxxxxxxxxxx",
  "aud": "example-app",
  "exp": 1551179050,
  "iat": 1551092650,
  "at_hash": "xxxxxxxxxxxxxxxxxxx",
  "email": "[email protected]",
  "email_verified": true,
  "groups": [
    "pokeorg",
    "pokeorg:admin",
    "pokeorg:developer"
  ],
  "name": "Peter Balogh",
  "federated_claims": {
    "connector_id": "github",
    "user_id": "13311234"
  }
}

example config:

app:
  addr: ":5555"

log:
  level: "4"
  format: "json"
  noColor: true

tokenhandler:
  oidc:
    clientID: example-app
    issuerURL: "http://dex/dex"

rbachandler:
  githubOrg: "pokeorg"
  customGroups:
  - groupName: developer
    customRules:
    - verbs: [ "get", "list" ]
      resources: [ "deployments", "replicasets", "pods" ]
      apiGroups: [ "", "extensions", "apps" ]
    namespaces: ["example_namespace"] # Only if you want to isolate the customRules to some namespaces, if you want that the customRules to apply to all namespaces delete this hole line...
  kubeConfig: "/Users/poke/.kube/config"

Define custom CA cert or set insecure connection

[tokenhandler]
caCertPath = "/path/to/tls.crt"
insecure = false

Setting insecure conection in command line:

jwt-to-rbac --tokenhandler.insecure=true

So to conclude on the open source JWT-to-RBAC project - follow these stpes if you would like to try it or check it out already in action by subscribing to our free developer beta at https://beta.banzaicloud.io/.

1. Deploy jwt-to-rbac to Kubernetes

After you cloning the GitHub repository you can compile a code and make a docker image with one command.

make docker

If you are using docker-for-desktop or minikube, you'll be able to deploy it using locally with the newly built image.

kubectl create -f deploy/rbac.yaml
kubectl create -f deploy/configmap.yaml
kubectl create -f deploy/deployment.yaml
kubectl create -f deploy/service.yaml
# port-forward locally
kubectl port-forward svc/jwt-to-rbac 5555

Now you can communicate with the jwt-to-rbac app.

2. POST ID token issued by Oidc to jwt-to-rbac API

curl --request POST \
  --url http://localhost:5555/rbac/ \
  --header 'Content-Type: application/json' \
  --data '{"token": "example.jwt.token"}'

# response:
{
    "Email": "[email protected]",
    "Groups": [
        "admins",
        "developers"
    ],
    "FederatedClaims": {
        "connector_id": "ldap",
        "user_id": "cn=jane,ou=People,dc=example,dc=org"
    }
}

The ServiceAccount, ClusterRoles (if ID token has some defined custom groups we discussed) and ClusterRoleBindings are created.

Listing the created K8s resources:

curl --request GET \
  --url http://localhost:5555/rbac \
  --header 'Content-Type: application/json'

#response:
{
    "sa_list": [
        "janedoe-example-com"
    ],
    "crole_list": [
        "developers-from-jwt"
    ],
    "crolebind_list": [
        "janedoe-example-com-admin-binding",
        "janedoe-example-com-developers-from-jwt-binding"
    ]
}

3. GET the default K8s token of ServiceAccount

curl --request GET \
  --url http://localhost:5555/tokens/janedoe-example-com \
  --header 'Content-Type: application/json'

# response:
[
    {
        "name": "janedoe-example-com-token-m4gbj",
        "data": {
            "ca.crt": "example-ca-cer-base64",
            "namespace": "ZGVmYXVsdA==",
            "token": "example-k8s-sa-token-base64"
        }
    }
]

4. Generate a ServiceAccount token with TTL

curl --request POST \
  --url http://localhost:5555/tokens/janedoe-example-com \
  --header 'Content-Type: application/json'
  --data '{"duration": "12h30m"}'

# response:
[
    {
        "name": "janedoe-example-com-token-df3re",
        "data": {
            "ca.crt": "example-ca-cer-base64",
            "namespace": "ZGVmYXVsdA==",
            "token": "example-k8s-sa-token-with-ttl-base64"
        }
    }
]

Now you have a base64 encoded service account token.

5. Accessing with ServiceAccount token

You can use service account token from command line:

kubectl --token $TOKEN_TEST --server $APISERVER get po

Or create kubectl context with it:

export TOKEN=$(echo "example-k8s-sa-token-base64" | base64 -D)
kubectl config set-credentials "janedoe-example-com" --token=$TOKEN
# with kubectl config get-clusters you can get cluster name
kubectl config set-context "janedoe-example-com-context" --cluster="clustername" --user="janedoe-example-com" --namespace=default
kubectl config use-context janedoe-example-com-context
kubectl get pod

As a final note - since we use Dex, which is an identity service that uses OpenID Connect to drive authentication for other apps, any other supported connector can be used for authentication to Kubernetes.

jwt-to-rbac's People

Contributors

abrahamjoc avatar ahma avatar bonifaido avatar dependabot[bot] avatar j-zimnowoda avatar matyix avatar pbalogh-sa avatar pregnor avatar vigohe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jwt-to-rbac's Issues

Allow use of self signed certificates for dex

Hi there,

I'm hitting this when hooking up jwt-to-rbac to my dex test instance:

provider init failed: Get https://dex.example.com:5556/.well-known/openid-configuration: x509: certificate signed by unknown authority.

Is there a current way to allow insecure SSL or to provide a CA cert to use when connecting to dex?

Support for add rules only for certain namespaces

Now the rules you add are global ("ClusterRoleBinding"), there are certain requirements where you want to only add the rules to certain namespaces.

To do this jwt-to-rbac should support "RoleBinding".

cant overwrite a clusterrolebinding

Describe the bug

If you request a rbac token on a second time after a first success, the system cant generate it, because it cant overwrite the previews clusterrolebinding that was generated on the first request.

This is the error thrown on the logs:
{"level":"error","msg":"create clusterrolebinding failed: clusterrolebindings.rbac.authorization.k8s.io "xxxxxxx-from-jwt-binding" already exists","package":"rbachandler","service":"jwt-to-rbac","time":"2021-12-02T19:51:59Z"}

Steps to reproduce the issue:

Request a first rbac token with a proper jwt-token.

Get it, and request another token again.

The error is thrown.

Expected behavior

Get a new rbac token at a second time, without having errors.

Validate id token on service account token request

Is your feature request related to a problem? Please describe.
Currently, there is no identity validation on GET /tokens/{saNme} request
It means that any malicious actor can request SA token, if SA has been already created.

Describe the solution you'd like to see

  • add Authorization header that contains ID token for GET /tokens/<sa-name> request,
  • validate Authorization header content by validating ID token signature.

Add openapi v3 spec

Is your feature request related to a problem? Please describe.

  • Learning about api endpoints, models, responses.
  • Automating API client generation

Describe the solution you'd like to see
The openapi v3 is the standard for defining API in structured and sharing it with the others.
Users can explore REST API in web browser, e.g: https://petstore.swagger.io
There are many tools that supports client generation from the openapi spec, e.g.: https://github.com/OpenAPITools/openapi-generator.

Describe alternatives you've considered
n/a

P.S.
If this feature is welcome, then I am eager to help you with this.

Hot reload config and update RBAC rules

Whenever the config it's edited "jwt-to-rbac" should know and update the config in the running process. So when a new RBAC it's create the rules should reflect the changes in the config

Broken dependency chain

make docker returns the below error.

go: github.com/belogik/[email protected]: git fetch -f origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /go/pkg/mod/cache/vcs/5b0453a6aac74c86b31f0a771f02325d031eaf6ec0f0503f1cc20852de3abb0a: exit status 128:
	fatal: could not read Username for 'https://github.com': terminal prompts disabled

Verify JWT with any OIDC provider

Is your feature request related to a problem? Please describe.
From documentation, it seems that this solution works only with Dex.

Describe the solution you'd like to see
I would go for more generic approach as verifying JWT is quite straight forward.

Changing log level and applying it with helm does not take an effect

Describe the bug
Changing log level and applying it with helm does not take an effect.

Steps to reproduce the issue:

  1. In values.yaml change log.level to debug
  2. helm upgrade --install
  3. observe that ConfigMap has changed but application still does not print debug level messages.

Expected behaviour
Application changes it log level

Use github actions

Is your feature request related to a problem? Please describe.
Using GitHub actions for build and test.

Describe the solution you'd like to see
Implement similar automation used in the case of Circleci.

Automate docker image build.

Is your feature request related to a problem? Please describe.
Automate docker image build.

Describe the solution you'd like to see
Trigger a docker image building and pushing if a tag is created.

Describe alternatives you've considered
Manually build and push the docker image

Hot reload of `customGroups` not working

Describe the bug

When you change the ConfigMap jwt-to-rbac at the level of customGroups at runtime, this changes doesn't applied.

This is because this conditions will be always nil because ClusterRole or ClusterRoleBinding already exists.

if err := rh.getAndCheckCRoleBinding(crb.name); err == nil {
return nil
}

if err := rh.getAndCheckCRole(cr.name); err == nil {
return nil
}

Steps to reproduce the issue:
Change something at customGroups level

Expected behavior

When someone change something at customGroups the ClusterRole & ClusterRoleBinding changes should be reflected

Create service account fails

I posted my valid JWT to the jwt-to-rbac service and got this error back:

create serviceaccount failed: ServiceAccount "" is invalid: metadata.name: Required value: name or generateName is required

Tracked down to here:

return emperror.WrapWith(err, "create serviceaccount failed", "saName", sa)

And relevant line here:

saName = user.FederatedClaimas.UserID

My JWT structure issued from dex using the github connector:

{
  "groups": [
    "org:group1",
    "org:group2"
  ],
  "sub": "sub",
  "iss": "https://dex.example.com",
  "email_verified": true,
  "name": "Daniel Whatmuff",
  "at_hash": "hash",
  "exp": 1550770904,
  "iat": 1550767304,
  "email": "myemail",
  "aud": "kubernetes"
}

If its trying to use name obviously the creation will fail with something like:

The ServiceAccount "Daniel Whatmuff" is invalid: metadata.name: Invalid value: "Daniel Whatmuff": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')

But looks like its trying to use FederatedClaimas.UserID...?

Also, I noticed a few FederatedClaimas - are they typos of FederatedClaims?

useless replace usage in go.mod

It seems that banzaicloud/jwt-to-rbac does not depend on github.com/belogik/goes any more, both directly and indirectly.
So, replace usage left in go.mod makes no sense. Should it be dropped?

$ go mod why -m github.com/OwnLocal/goes
# github.com/OwnLocal/goes
(main module does not need module github.com/OwnLocal/goes)

https://github.com/banzaicloud/jwt-to-rbac/blob/master/go.mod#L55

replace github.com/belogik/goes => github.com/OwnLocal/goes v1.0.0

Document required user claims

Is your feature request related to a problem? Please describe.
Required user claims are not documented.
E.g.: What are FederatedClaims and how they are used by jwt-to-rbac?

Describe the solution you'd like to see
All requirements that OIDC provider needs to meet in order to be compatible with jwt-to-rbac

Leveraging SA token to access EKS cluster

Is your feature request related to a problem? Please describe.

Having just SA token is not sufficient to access EKS cluster.

Describe the solution you'd like to see
Since this solution aims to solve managed oidc authentication issue for managed clusters, It would be great to show how to do it for the biggest cloud providers: AKS, EKS, GKS

Describe alternatives you've considered
n/a

Additional context
For EKS clusters

performing just:

k --token $token --certificate-authority ca.crt --server $APISERVER get po 
error: You must be logged in to the server (Unauthorized)

after annotating service account with AWS role:

k --token $token --certificate-authority ca.crt --server $APISERVER get po 
error: the server doesn't have a resource type "po"

Do not allow clients to set TTL for SA token

Is your feature request related to a problem? Please describe.
The SA token TTL is a policy that cluster administrator should enforce, not a user that is requesting SA token.

Describe the solution you'd like to see
I propose to

Describe alternatives you've considered
n/a

Move chart into the project

Is your feature request related to a problem? Please describe.

  • Move jwt-to-rbac chart into the project.
  • Update the chart in order to use new features

Describe the solution you'd like to see
Build chart triggered by tagging similarly to #24

The response for HTTP POST /rbac/

Hi, first of all thanks for documentation update.
I see a big potential in this project, so I would to contribute more ;).

Is your feature request related to a problem? Please describe.
After sending ID_TOKEN to /rbac/ endpoint, a new Service Account (SA) is created.
We need to know its name in order to ask request SA token. Unfortunately, the response does not contain SA name, thus a user needs to figure it out.

Describe the solution you'd like to see
I propose to add a new property: ServiceAccount to POST /rbac/ response:

{
    "Email": "[email protected]",
    "Groups": [
        "admins",
        "developers"
    ],
    "FederatedClaims": {
        "connector_id": "ldap",
        "user_id": "cn=jane,ou=People,dc=example,dc=org"
    }
    // New property
    "ServiceAccount": "janedoe-example-com"
}

Describe alternatives you've considered
n/a
Additional context
n/a

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.