gravitational / teleport

The easiest, and most secure way to access and protect all of your infrastructure.
https://goteleport.com
GNU Affero General Public License v3.0
17.52k stars 1.75k forks source link

OIDC Joining support #14293

Closed strideynet closed 2 years ago

strideynet commented 2 years ago

An RFD should be produced for this work prior to its initiation. RFD 50 should also be updated to include this new join method.

We currently support joining with a token, or joining via AWS IAM. In order to support a variety of platforms, we could also support trusting OIDC providers. The two platforms that come to mind initially for this feature are Github Actions and GCP. These platforms allow a workload to access its own identity (usually via some endpoint on a metadata server), and by trusting their OIDC provider, we can allow them to join a Teleport cluster using this identity.

At the moment on these platforms, users are required to create and securely store join tokens. This is a security issue as these tokens can be long-lived, and present a significant risk if they were to be exfiltrated.

This work is essentially two parts:

Trust rules

We must always ensure that the token has an audience claim equal to the Teleport cluster name.

We will need to allow the user to constrain what identities are accepted based on the claims in the token.

For example, a user configuring this for GHA may want to ensure that only a certain repository/workflow is able to access Teleport. A user on GCP may want to ensure that only certain VMs are able to access Teleport.

We could use a language such as CEL (this is what GCP uses) to allow the user the most flexibility in configuring this. We could also have provider specific options to allow a faster time to value.

Example using simple matching on the claim names:

kind: token
version: v2
metadata:
  name: gha-token
  expires: "3000-01-01T00:00:00Z"
spec:
  roles: [Node]
  join_method: oidc
  issuer_url: https://token.actions.githubusercontent.com/
  allow:
    - repository: "gravitational/teleport"
       workflow: foo
    - repository: "gravitational/teleport"
       workflow: bar

I fear this may be too simplistic in cases where the JWT claims include arrays or maps.

Example using a CEL expression:

kind: token
version: v2
metadata:
  name: gha-token
  expires: "3000-01-01T00:00:00Z"
spec:
  roles: [Node]
  join_method: oidc
  issuer_url: https://token.actions.githubusercontent.com/
  allow: claims.repository == "gravitational/teleport" && (claims.workflow == "foo" || claims.workflow == "bar")

This is a little more complicated for users, but once they are accustomed to CEL, is way more powerful and would be well suited to complex claims.

GCP Research

### GCE https://cloud.google.com/compute/docs/metadata/overview#querying ```sh curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://noah.teleport.sh&format=full" -H "Metadata-Flavor: Google" ``` Produces a JWT that contains: ```json { "aud": "https://noah.teleport.sh", "azp": "REDACTED", "email": "REDACTED-compute@developer.gserviceaccount.com", "email_verified": true, "exp": 1657540679, "google": { "compute_engine": { "instance_creation_timestamp": 1657536818, "instance_id": "REDACTED", "instance_name": "test-instance-noah", "project_id": "REDACTED", "project_number": REDACTED, "zone": "us-central1-a" } }, "iat": 1657537079, "iss": "https://accounts.google.com", "sub": "REDACTED" } ``` ```sh curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://noah.teleport.sh" -H "Metadata-Flavor: Google" ``` Produces a JWT that contains: ```json { "aud": "https://noah.teleport.sh", "azp": "REDACTED", "exp": 1657540714, "iat": 1657537114, "iss": "https://accounts.google.com", "sub": "REDACTED" } ``` These can be verified using https://accounts.google.com/.well-known/openid-configuration ### GCR https://cloud.google.com/run/docs/container-contract#metadata-server GCR is likely out of scope for an initial implementation. There's additional complexity in how we would run MachineID or similar alongside the customer container.

GHA Research

The GHA provided token has the following claims: ```json { "jti": "example-id", "sub": "repo:octo-org/octo-repo:environment:prod", "environment": "prod", "aud": "https://github.com/octo-org", "ref": "refs/heads/main", "sha": "example-sha", "repository": "octo-org/octo-repo", "repository_owner": "octo-org", "actor_id": "12", "repository_id": "74", "repository_owner_id": "65", "run_id": "example-run-id", "run_number": "10", "run_attempt": "2", "actor": "octocat", "workflow": "example-workflow", "head_ref": "", "base_ref": "", "event_name": "workflow_dispatch", "ref_type": "branch", "job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main", "iss": "https://token.actions.githubusercontent.com", "nbf": 1632492967, "exp": 1632493867, "iat": 1632493567 } ```

References

Platforms that provide OIDC tokens that we could trust for joining:

Other platforms that support trusting an OIDC provider:

Closes #12536

strideynet commented 2 years ago

I'm fairly convinced that an initial implementation of this should target an individual provider as to keep the PR size down. I'm split between GHA and GCP, and am open to suggestions as to which we should prefer. Whilst GHA is probably more useful from a Machine ID point of view, I think that GCP is probably better as it's a cloud provider we are aiming to target more and automatic node joining would be a large benefit on that platform.