Compare commits

...

628 Commits

Author SHA1 Message Date
Mo Khan
f015ad5852 Merge pull request #405 from enj/enj/i/cluster_scope_concierge
Cluster scope all concierge APIs
2021-02-11 08:50:42 -05:00
Monis Khan
b04fd46319 Update federation domain logic to use status subresource
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:10 -05:00
Monis Khan
4c304e4224 Assert all APIs have a status subresource
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:10 -05:00
Monis Khan
0a9f446893 Update credential issuer logic to use status subresource
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:10 -05:00
Monis Khan
96cec59236 Generated
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:09 -05:00
Monis Khan
4faf724c2c Make credential issuer status optional
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:09 -05:00
Monis Khan
de88ae2f61 Fix status related RBAC
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:09 -05:00
Monis Khan
dd3d1c8b1b Generated
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:09 -05:00
Monis Khan
2e9baf9fa6 Correctly generate status subresource for all CRDs
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:08 -05:00
Monis Khan
ac01186499 Use API service as owner ref for cluster scoped resources
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:08 -05:00
Monis Khan
2eb01bd307 authncache: remove namespace concept
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:08 -05:00
Monis Khan
741b8fe88d Generated
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:08 -05:00
Monis Khan
d25c6d9d0a Make kubebuilder CRDs cluster scoped
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:08 -05:00
Monis Khan
89b00e3702 Declare war on namespaces
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:07 -05:00
Monis Khan
d2480e6300 Generated
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:07 -05:00
Monis Khan
4205e3dedc Make concierge APIs cluster scoped
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-10 21:52:07 -05:00
Matt Moyer
ee80920ffd Merge pull request #409 from mattmoyer/upgrade-debian
Upgrade Debian base images from 10.7 to 10.8.
2021-02-10 16:57:09 -06:00
Matt Moyer
45f4a0528c Upgrade Debian base images from 10.7 to 10.8.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-10 15:57:16 -06:00
Andrew Keesler
d0266cecdb Merge pull request #390 from ankeesler/use-more-middleware
Use middleware to mutate TokenCredentialRequest.Spec.Authenticator.APIGroup
2021-02-10 16:38:54 -05:00
Andrew Keesler
0fc1f17866 internal/groupsuffix: mutate TokenCredentialRequest's Authenticator
This is a partial revert of 288d9c999e. For some reason it didn't occur to me
that we could do it this way earlier. Whoops.

This also contains a middleware update: mutation funcs can return an error now
and short-circuit the rest of the request/response flow. The idea here is that
if someone is configuring their kubeclient to use middleware, they are agreeing
to a narrow-er client contract by doing so (e.g., their TokenCredentialRequest's
must have an Spec.Authenticator.APIGroup set).

I also updated some internal/groupsuffix tests to be more realistic.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-10 15:53:44 -05:00
Andrew Keesler
ae6503e972 internal/plog: add KObj() and KRef()
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-10 14:25:39 -05:00
Mo Khan
44b7679e9f Merge pull request #407 from ankeesler/test-flake
test/integration: make TestKubeCertAgent more stable
2021-02-10 14:24:44 -05:00
Andrew Keesler
12d5b8959d test/integration: make TestKubeCertAgent more stable
I think the reason we were seeing flakes here is because the kube cert agent
pods had not reached a steady state even though our test assertions passed, so
the test would proceed immediately and run more assertions on top of a weird
state of the kube cert agent pods.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-10 12:08:34 -05:00
Andrew Keesler
5b076e7421 Merge pull request #404 from ankeesler/remove-deprecated-commands
cmd/pinniped: delete get-kubeconfig + exchange-token
2021-02-10 08:33:00 -05:00
Andrew Keesler
1ffe70bbea cmd/pinniped: delete get-kubeconfig + exchange-token
These were deprecated in v0.3.0.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-09 17:01:57 -05:00
Mo Khan
cf735715f6 Merge pull request #394 from enj/enj/i/server_side_tcr_api_group
Use server scheme to handle credential request API group changes
2021-02-09 16:36:13 -05:00
Monis Khan
2679d27ced Use server scheme to handle credential request API group changes
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-09 15:51:38 -05:00
Monis Khan
6b71b8d8ad Revert server side token credential request API group changes
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-09 15:51:35 -05:00
Andrew Keesler
43da4ab2e0 SECURITY.md: follow established pattern
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-09 09:08:19 -05:00
Matt Moyer
e4d8af6701 Merge pull request #399 from mattmoyer/upgrade-go
Upgrade Go from 1.15.7 to 1.15.8.
2021-02-08 18:17:17 -06:00
Matt Moyer
d06c935c2c Upgrade Go from 1.15.7 to 1.15.8.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-08 10:58:51 -06:00
Mo Khan
9399b5d800 Merge pull request #395 from enj/enj/i/remove_multierror
Remove multierror package and migrate callers to k8s.io/apimachinery/pkg/util/errors.NewAggregate
2021-02-05 15:14:25 -05:00
Monis Khan
05a471fdf9 Migrate callers to k8s.io/apimachinery/pkg/util/errors.NewAggregate
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-05 12:56:05 -05:00
Monis Khan
81d4e50f94 Remove multierror package
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-05 12:55:18 -05:00
Matt Moyer
850f030fe3 Merge pull request #393 from enj/enj/i/no_op_tcr_list
Add no-op list support to token credential request
2021-02-05 11:09:09 -06:00
Monis Khan
f7958ae75b Add no-op list support to token credential request
This allows us to keep all of our resources in the pinniped category
while not having kubectl return errors for calls such as:

kubectl get pinniped -A

Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-05 10:59:39 -05:00
Andrew Keesler
ee05f155ca Merge pull request #392 from ankeesler/flowcontrol-rbac
deploy/concierge: add RBAC for flowschemas and prioritylevelconfigurations
2021-02-05 09:19:50 -05:00
Andrew Keesler
2ae631b603 deploy/concierge: add RBAC for flowschemas and prioritylevelconfigurations
As of upgrading to Kubernetes 1.20, our aggregated API server nows runs some
controllers for the two flowcontrol.apiserver.k8s.io resources in the title of
this commit, so it needs RBAC to read them.

This should get rid of the following error messages in our Concierge logs:
  Failed to watch *v1beta1.FlowSchema: failed to list *v1beta1.FlowSchema: flowschemas.flowcontrol.apiserver.k8s.io is forbidden: User "system:serviceaccount:concierge:concierge" cannot list resource "flowschemas" in API group "flowcontrol.apiserver.k8s.io" at the cluster scope
  Failed to watch *v1beta1.PriorityLevelConfiguration: failed to list *v1beta1.PriorityLevelConfiguration: prioritylevelconfigurations.flowcontrol.apiserver.k8s.io is forbidden: User "system:serviceaccount:concierge:concierge" cannot list resource "prioritylevelconfigurations" in API group "flowcontrol.apiserver.k8s.io" at the cluster scope

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-05 08:19:12 -05:00
Matt Moyer
9c64476aee Tweak some small bits in the blog post.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-04 17:51:35 -06:00
Matt Moyer
b6e98b5783 Update the get.pinniped.dev redirect to always point at the latest version.
I messed this up before because the ordering of the path components is a bit different than in the specific version case.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-04 17:48:41 -06:00
Matt Moyer
9addb4d6e0 Merge pull request #385 from vmware-tanzu/credential_request_spec_api_group
Use custom suffix in `Spec.Authenticator.APIGroup` of `TokenCredentialRequest`
2021-02-04 16:19:20 -06:00
Ryan Richard
2a921f7090 Merge branch 'main' into credential_request_spec_api_group 2021-02-04 13:44:53 -08:00
Matt Moyer
bb8b65cca6 Merge pull request #387 from vmware-tanzu/blog/multiple-pinnipeds
Add a v0.5.0 "multiple Pinnipeds" blog post.
2021-02-04 15:22:52 -06:00
Matt Moyer
5c331e9002 Fix go.pinniped.dev redirects.
Our meeting notes are now on HackMD, our Zoom link changed, and I added a YouTube link.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-04 14:56:50 -06:00
Matt Moyer
1382fc6e5f Add a v0.5.0 "multiple Pinnipeds" blog post. 2021-02-04 14:56:49 -06:00
Andrew Keesler
cc8c917249 Merge pull request #325 from ankeesler/restart-test
Add an integration test helper to assert that no pods restart during the test
2021-02-04 13:07:40 -05:00
Andrew Keesler
ae498f14b4 test/integration: ensure no pods restart during integration tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-04 10:24:33 -05:00
Ryan Richard
288d9c999e Use custom suffix in Spec.Authenticator.APIGroup of TokenCredentialRequest
When the Pinniped server has been installed with the `api_group_suffix`
option, for example using `mysuffix.com`, then clients who would like to
submit a `TokenCredentialRequest` to the server should set the
`Spec.Authenticator.APIGroup` field as `authentication.concierge.mysuffix.com`.

This makes more sense from the client's point of view than using the
default `authentication.concierge.pinniped.dev` because
`authentication.concierge.mysuffix.com` is the name of the API group
that they can observe their cluster and `authentication.concierge.pinniped.dev`
does not exist as an API group on their cluster.

This commit includes both the client and server-side changes to make
this work, as well as integration test updates.

Co-authored-by: Andrew Keesler <akeesler@vmware.com>
Co-authored-by: Ryan Richard <richardry@vmware.com>
Co-authored-by: Margo Crawford <margaretc@vmware.com>
2021-02-03 15:49:15 -08:00
Andrew Keesler
26922307ad prepare-for-integration-tests.sh: New cmdline option --api_group_suffix
Makes it easy to deploy Pinniped under a different API group for manual
testing and iterating on integration tests on your laptop.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2021-02-03 12:07:38 -08:00
Ryan Richard
5549a262b9 Rename client_test.go to concierge_client_test.go
Because it is a test of the conciergeclient package, and the naming
convention for integration test files is supervisor_*_test.go,
concierge_*_test.go, or cli_*_test.go to identify which component
the test is primarily covering.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-03 12:07:38 -08:00
Mo Khan
c5df66fbd5 Merge pull request #383 from enj/enj/i/avoid_scheme_double_register
Avoid double registering types in server scheme
2021-02-03 13:55:33 -05:00
Monis Khan
300d7bd99c Drop duplicate logic for unversioned type registration
Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-03 12:16:57 -05:00
Monis Khan
012bebd66e Avoid double registering types in server scheme
This makes sure that if our clients ever send types with the wrong
group, the server will refuse to decode it.

Signed-off-by: Monis Khan <mok@vmware.com>
2021-02-03 12:16:57 -05:00
Andrew Keesler
e1d06ce4d8 internal/mocks/mockroundtripper: we don't need these anymore
We thought we needed these to test the middleware, but we don't.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-03 08:55:38 -05:00
Andrew Keesler
52b98bdb87 Merge pull request #330 from enj/enj/f/better_middleware
Enhance middleware to allow multiple Pinnipeds
2021-02-03 08:53:00 -05:00
Andrew Keesler
62c117421a internal/kubeclient: fix not found test and request body closing bug
- I realized that the hardcoded fakekubeapi 404 not found response was invalid,
  so we were getting a default error message. I fixed it so the tests follow a
  higher fidelity code path.
- I caved and added a test for making sure the request body was always closed,
  and believe it or not, we were double closing a body. I don't *think* this will
  matter in production, since client-go will pass us ioutil.NopReader()'s, but
  at least we know now.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-03 08:19:34 -05:00
Monis Khan
efe1fa89fe Allow multiple Pinnipeds to work on same cluster
Yes, this is a huge commit.

The middleware allows you to customize the API groups of all of the
*.pinniped.dev API groups.

Some notes about other small things in this commit:
- We removed the internal/client package in favor of pkg/conciergeclient. The
  two packages do basically the same thing. I don't think we use the former
  anymore.
- We re-enabled cluster-scoped owner assertions in the integration tests.
  This code was added in internal/ownerref. See a0546942 for when this
  assertion was removed.
- Note: the middlware code is in charge of restoring the GV of a request object,
  so we should never need to write mutations that do that.
- We updated the supervisor secret generation to no longer manually set an owner
  reference to the deployment since the middleware code now does this. I think we
  still need some way to make an initial event for the secret generator
  controller, which involves knowing the namespace and the name of the generated
  secret, so I still wired the deployment through. We could use a namespace/name
  tuple here, but I was lazy.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
Co-authored-by: Ryan Richard <richardry@vmware.com>
2021-02-02 15:18:41 -08:00
Andrew Keesler
93d25a349f hack: fix docker most recent tag check
I think this stopped working when we starting using a specific registry in e0b94f47.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-02 18:01:07 -05:00
Andrew Keesler
93ebd0f949 internal/plog: add Enabled()
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-02-02 18:01:06 -05:00
Matt Moyer
74a8005f92 Merge pull request #376 from mattmoyer/add-csrftoken-test
Add some trivial unit tests to internal/oidc/csrftoken.
2021-02-02 11:02:39 -06:00
Matt Moyer
5b4e58f0b8 Add some trivial unit tests to internal/oidc/csrftoken.
This change is primarily to test that our test coverage reporting is working as expected.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-02 09:38:17 -06:00
Matt Moyer
b871a02ca3 Merge pull request #375 from mattmoyer/test-coverage
Add Codecov configuration file.
2021-02-01 15:19:37 -06:00
Matt Moyer
6a20bbf607 Add Codecov configuration file.
This configures how our coverage reports are processed on https://codecov.io. See https://docs.codecov.io/docs/codecov-yaml for reference.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-02-01 14:28:38 -06:00
Ryan Richard
dfa4d639e6 Merge pull request #374 from microwavables/main
Updated the community meeting info with new zoom link and agenda notes
2021-01-29 14:15:17 -08:00
Nanci Lancaster
8b4024bf82 Updated the community meeting info with new zoom link and agenda notes
Signed-off-by: Nanci Lancaster <nancil@vmware.com>
2021-01-29 16:07:23 -06:00
Ryan Richard
d89c6546e7 Merge pull request #373 from microwavables/main
Updated text on community meetings and added YouTube link
2021-01-28 09:49:12 -08:00
Nanci Lancaster
2710591429 Updated text on community meetings and added YouTube link
Signed-off-by: Nanci Lancaster <nancil@vmware.com>
2021-01-28 11:22:44 -06:00
Matt Moyer
02815cfb26 Revert "Use GitHub's "latest" handling so this doesn't get out of sync."
This reverts commit 46ad41e813.

This turns out not to work, so we have to use a hardcoded version here.
2021-01-28 10:28:46 -06:00
Matt Moyer
3f7cb5d9f8 Merge pull request #372 from mattmoyer/fix-redirects-version
Fix get.pinniped.dev latest version redirects.
2021-01-28 10:26:51 -06:00
Matt Moyer
46ad41e813 Use GitHub's "latest" handling so this doesn't get out of sync.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-28 10:25:33 -06:00
Matt Moyer
d4eca3a82a Fix get.pinniped.dev latest version redirects.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-28 10:23:48 -06:00
Matt Moyer
c03a088399 Merge pull request #370 from mattmoyer/cleanup-docs
Clean up docs using https://get.pinniped.dev redirects.
2021-01-28 10:17:46 -06:00
Matt Moyer
f81dda4eda Add syntax highlighting CSS.
This was generated via `hugo gen chromastyles --style=monokailight > ./site/themes/pinniped/assets/scss/_syntax.css`.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-28 10:15:39 -06:00
Matt Moyer
1ceef5874e Clean up docs using https://get.pinniped.dev redirects.
We have these redirects set up to make the `kubectl apply -f [...]` commands cleaner, but we never went back and fixed up the documentation to use them until now.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-28 10:15:39 -06:00
Matt Moyer
1b224bc4f2 Merge pull request #369 from mattmoyer/cleanup-go-sum
Prune unused versions from go.sum.
2021-01-28 10:09:06 -06:00
Matt Moyer
530d6961c2 Prune unused versions from go.sum.
The broken github.com/oleiade/reflections v1.0.0 package was still causing problems with Dependabot.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-28 09:03:00 -06:00
Matt Moyer
fe500882ef Merge pull request #365 from mattmoyer/upgrade-oleiade-reflections-dep
Upgrade github.com/oleiade/reflections to v1.0.1.
2021-01-27 15:56:49 -06:00
Matt Moyer
8358c26107 Upgrade github.com/oleiade/reflections to v1.0.1.
This project overwrote the v1.0.0 tag with a different commit ID, which has caused issues with the Go module sum DB (which accurately detected the issue).

This has been one of the reasons why Dependabot is not updating our Go dependencies.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-27 13:49:30 -06:00
Matt Moyer
ad9a187522 Merge pull request #335 from mattmoyer/optimize-dockerfile
Optimize image build using .dockerignore and BuildKit features.
2021-01-27 11:35:42 -06:00
Matt Moyer
8a41419b94 Optimize image build using .dockerignore and BuildKit features.
This optimizes our image in a few different ways:

- It adds a bunch of files and directories to the `.dockerignore` file.
  This lets us have a single `COPY . .` but still be very aggressive about pruning what files end up in the build context.

- It adds build-time cache mounts to the `go build` commands using BuildKit's `--mount=type=cache` flag.
  This requires BuildKit-capable Docker, but means that our Go builds can all be incremental builds.
  This replaces the previous flow we had where we needed to split out `go mod download`.

- Instead of letting the full `apt-get install ca-certificates` layer end up in our final image, we copy just the single file we need.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-27 10:42:56 -06:00
Ryan Richard
6ef7ec21cd Merge branch 'release-0.4' into main 2021-01-25 15:13:14 -08:00
Ryan Richard
b77297c68d Validate the upstream email_verified claim when it makes sense 2021-01-25 15:10:41 -08:00
Ryan Richard
df1d15ebd1 Merge pull request from GHSA-wp53-6256-whf9
This is a fake PR for testing - please ignore
2021-01-22 12:46:53 -08:00
Ryan Richard
b3732e8b6c Trivial change to a comment 2021-01-22 12:43:35 -08:00
Matt Moyer
7e887666ce Merge pull request #349 from microwavables/main
Add Google Group for meetings
2021-01-21 15:15:01 -06:00
Nanci Lancaster
d6e6f51ced Add Google Group for meetings
Signed-off-by: Nanci Lancaster <nancil@vmware.com>
2021-01-21 14:57:14 -06:00
Matt Moyer
9e21de9c47 Merge pull request #347 from mattmoyer/upgrade-go-oidc-library
Upgrade to github.com/coreos/go-oidc v3.0.0.
2021-01-21 14:39:22 -06:00
Matt Moyer
04c4cd9534 Upgrade to github.com/coreos/go-oidc v3.0.0.
See https://github.com/coreos/go-oidc/releases/tag/v3.0.0 for release notes.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-21 12:08:14 -06:00
Matt Moyer
5821faec03 Merge pull request #342 from vmware-tanzu/pre-commit-fix
Remove pre-commit hooks file to de-duplicate from pre-commit-config
2021-01-21 12:02:11 -06:00
Matt Moyer
8bca244d59 Merge pull request #345 from vmware-tanzu/dependabot/docker/golang-1.15.7
Bump golang from 1.15.6 to 1.15.7
2021-01-21 11:31:06 -06:00
dependabot[bot]
79fa96cfbc Bump golang from 1.15.6 to 1.15.7
Bumps golang from 1.15.6 to 1.15.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 13:56:04 +00:00
Ryan Richard
b5cbe018e3 Allow passing multiple redirect URIs to Dex
We need this in CI when we want to configure Dex with the redirect URI for both
primary and secondary deploys at one time (since we only stand up Dex once).

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-20 17:06:50 -05:00
Andrew Keesler
33f4b671d1 Merge pull request #327 from ankeesler/reenable-max-inflight-checks
Restore max in flight check when updating to 0.19.5 #243
2021-01-19 18:29:38 -05:00
Andrew Keesler
50c3e4c00f Merge branch 'main' into reenable-max-inflight-checks 2021-01-19 18:14:27 -05:00
Andrew Keesler
5486427d88 Merge pull request #344 from vmware-tanzu/wire-api-group-suffix
Wire api group suffix through YTT/server components/CLI/integration tests
2021-01-19 18:06:12 -05:00
Andrew Keesler
906bfa023c test: wire API group suffix through to tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-19 17:23:20 -05:00
Andrew Keesler
1c3518e18a cmd/pinniped: wire API group suffix through to client components
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-19 17:23:20 -05:00
Andrew Keesler
88fd9e5c5e internal/config: wire API group suffix through to server components
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-19 17:23:20 -05:00
Ryan Richard
616211c1bc deploy: wire API group suffix through YTT templates
I didn't advertise this feature in the deploy README's since (hopefully) not
many people will want to use it?

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-19 17:23:06 -05:00
Andrew Keesler
7a9c0e8c69 Merge branch 'main' into reenable-max-inflight-checks 2021-01-19 13:53:00 -05:00
Margo Crawford
c09020102c Remove pre-commit hooks file 2021-01-19 09:43:11 -08:00
Andrew Keesler
af11d8cd58 Run Tilt images as root for faster reload
Previously, when triggering a Tilt reload via a *.go file change, a reload would
take ~13 seconds and we would see this error message in the Tilt logs for each
component.

  Live Update failed with unexpected error:
    command terminated with exit code 2
  Falling back to a full image build + deploy

Now, Tilt should reload images a lot faster (~3 seconds) since we are running
the images as root.

Note! Reloading the Concierge component still takes ~13 seconds because there
are 2 containers running in the Concierge namespace that use the Concierge
image: the main Concierge app and the kube cert agent pod. Tilt can't live
reload both of these at once, so the reload takes longer and we see this error
message.

  Will not perform Live Update because:
    Error retrieving container info: can only get container info for a single pod; image target image:image/concierge has 2 pods
  Falling back to a full image build + deploy

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-15 11:34:53 -05:00
Matt Moyer
93ba1b54f2 Merge branch 'main' into reenable-max-inflight-checks 2021-01-15 10:19:17 -06:00
Matt Moyer
156e8d9df4 Merge pull request #334 from mattmoyer/fix-test-e2e-full-integration-groups-assertion
Fix an issue in TestE2EFullIntegration groups assertions.
2021-01-14 21:22:13 -06:00
Matt Moyer
6a0dc1e2bb Fix an issue in TestE2EFullIntegration groups assertions.
The group claims read from the session cache file are loaded as `[]interface{}` (slice of empty interfaces) so when we previously did a `groups, _ := idTokenClaims[oidc.DownstreamGroupsClaim].([]string)`, then `groups` would always end up nil.

The solution I tried here was to convert the expected value to also be `[]interface{}` so that `require.Equal(t, ...)` does the right thing.

This bug only showed up in our acceptance environnment against Okta, since we don't have any other integration test coverage with IDPs that pass a groups claim.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2021-01-14 21:06:02 -06:00
Margo Crawford
b95f2c97b9 Merge pull request #333 from vmware-tanzu/groups-claim-parsing
groups claim parsing
2021-01-14 15:55:42 -08:00
Margo Crawford
d11a73c519 PR feedback-- omit empty groups, keep groups as nil until last minute
Also log keys and values for claims
2021-01-14 15:11:00 -08:00
Andrew Keesler
6fce1bd6bb Allow arrays of type interface
and always set the groups claim to an
array in the downstream token

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2021-01-14 17:21:41 -05:00
Margo Crawford
5e60c14ce7 internal/upstreamoidc: log claims from ID token and userinfo
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-14 16:47:39 -05:00
Andrew Keesler
434448a2f9 Merge pull request #331 from ankeesler/1-20-owner-ref-test
Update test/integration/kubeclient_test.go to work with Kube 1.20 GC behavior
2021-01-14 10:59:02 -05:00
Andrew Keesler
8a916ce8ae test/integration: add test helper to avoid race conditions
We were seeing a race in this test code since the require.NoError() and
require.Eventually() would write to the same testing.T state on separate
goroutines. Hopefully this helper function should cover the cases when we want
to require.NoError() inside a require.Eventually() without causing a race.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
Co-authored-by: Margo Crawford <margaretc@vmware.com>
Co-authored-by: Monis Khan <i@monis.app>
2021-01-14 10:19:35 -05:00
Andrew Keesler
a0546942b8 test/integration: skip part of test to avoid Kube 1.20 GC bug
See comment.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
Co-authored-by: Margo Crawford <margaretc@vmware.com>
Co-authored-by: Monis Khan <i@monis.app>
2021-01-14 10:19:26 -05:00
Andrew Keesler
792bb98680 Revert "Temporarily disable max inflight checks for mutating requests"
This reverts commit 4a28d1f800.

This commit was originally made to fix a bug that caused TokenCredentialRequest
to become slow when the server was idle for an extended period of time. This was
to address a Kubernetes issue that was fixed in 1.19.5 and onward. We are now
running with Kubernetes 1.20, so we should be able to pick up this fix.
2021-01-13 11:12:09 -05:00
Andrew Keesler
3151ca92db Merge pull request #322 from enj/enj/f/user_info_test
Wire in new env vars for user info testing
2021-01-12 11:51:46 -05:00
Monis Khan
3c3da9e75d Wire in new env vars for user info testing
Signed-off-by: Monis Khan <mok@vmware.com>
2021-01-12 11:23:25 -05:00
Mo Khan
3f08f2e11e Merge pull request #318 from enj/enj/f/user_info_endpoint
Fetch claims from the user info endpoint if provided
2021-01-11 14:14:20 -05:00
Monis Khan
6fff179e39 Fetch claims from the user info endpoint if provided
Signed-off-by: Monis Khan <mok@vmware.com>
2021-01-09 18:16:24 -05:00
Margo Crawford
3569076d3e Merge pull request #317 from vmware-tanzu/kubernetes-1.20
Switching to Kubernetes 1.20
2021-01-08 15:31:48 -08:00
Margo Crawford
2686031ac1 Fixing documentation to reference 1.20 generated docs 2021-01-08 15:21:23 -08:00
Margo Crawford
9051342d6d Ignore lint error 2021-01-08 14:13:04 -08:00
Margo Crawford
6f04613aed Merge branch 'main' of github.com:vmware-tanzu/pinniped into kubernetes-1.20 2021-01-08 13:22:31 -08:00
Margo Crawford
326f10bbbf Resolving code review suggestions:
- set provideClusterInfo to true
- kubernetes library versions to 0.20.1
- version timestamps back to v0.0.0-00010101000000-000000000000
2021-01-08 10:21:59 -08:00
Mo Khan
6a9976742c Merge pull request #316 from enj/enj/i/always_set_owner_ref
Always set an owner ref back to our deployment
2021-01-07 19:51:02 -05:00
Margo Crawford
1b770b01ae Fix failing kubeconfig unit test 2021-01-07 16:23:41 -08:00
Margo Crawford
5611212ea9 Changing references from 1.19 to 1.20 2021-01-07 15:25:47 -08:00
Margo Crawford
b8f56bd10b 1.20 Changes to the update script and Dockerfile 2021-01-07 13:20:25 -08:00
Monis Khan
bba0f3a230 Always set an owner ref back to our deployment
This change updates our clients to always set an owner ref when:

1. The operation is a create
2. The object does not already have an owner ref set

Signed-off-by: Monis Khan <mok@vmware.com>
2021-01-07 15:25:40 -05:00
Margo Crawford
9b8e4f4d5b Merge pull request #315 from vmware-tanzu/kube-versions-1.20.0
Kubernetes 1.20.0 generated code
2021-01-07 10:47:52 -08:00
Margo Crawford
b7cd026bd6 Merge branch 'main' of github.com:vmware-tanzu/pinniped into kube-versions-1.20.0 2021-01-07 10:30:40 -08:00
Margo Crawford
553e25cbb7 Add generated/1.20 directory 2021-01-07 10:29:56 -08:00
Margo Crawford
988eee82cf Merge pull request #314 from vmware-tanzu/kube-versions-1.20.0
Add kubernetes 1.20 to kube-versions.txt
2021-01-07 09:57:36 -08:00
Margo Crawford
da1bf06764 Add kubernetes 1.20 to kube-versions.txt 2021-01-07 09:51:45 -08:00
Andrew Keesler
13d17ba352 Merge pull request #312 from ankeesler/credential-issuer-test-timing
test/integration: fix intermittent failures on GKE
2021-01-06 14:58:06 -05:00
Andrew Keesler
3d8616e75f test/integration: fix intermittent failures on GKE
See comment. This is at least a first step to make our GKE acceptance
environment greener. Previously, this test assumed that the Pinniped-under-test
had been deployed in (roughly) the last 10 minutes, which is not an assumption
that we make anywhere else in the integration test suite.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-06 12:09:11 -05:00
Margo Crawford
e7884d8793 Merge pull request #313 from vmware-tanzu/copyright-year
Copyright year validation in linter and pre-commit hook
2021-01-06 09:08:19 -08:00
Margo Crawford
19d592566d Merge branch 'main' into copyright-year 2021-01-06 09:03:13 -08:00
Margo Crawford
afa140b6a6 Add more text explaining what copyright notice should look like 2021-01-05 16:06:59 -08:00
Margo Crawford
ea6ebd0226 Got pre-commit to check for correct copyright year 2021-01-05 15:53:14 -08:00
Andrew Keesler
53a185083c Hopefully triggering the precommit hook
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2021-01-05 14:15:46 -08:00
Margo Crawford
f1e177fee7 Copyright year precommit hook
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-05 14:02:28 -08:00
Andrew Keesler
75bc5bdc7e Linter allows range of years in copyright
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2021-01-05 13:35:09 -08:00
Margo Crawford
0d4588aa8d Merge pull request #311 from vmware-tanzu/dont-block-owner-deletion
Remove blockOwnerDeletion from the supervisor secrets
2021-01-05 13:18:39 -08:00
Andrew Keesler
40753d1454 Remove blockOwnerDeletion from the supervisor secrets
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2021-01-05 10:44:36 -08:00
Andrew Keesler
dd3c990a51 Merge pull request #310 from vmware-tanzu/supervisor-demo
Supervisor demo
2021-01-05 09:57:53 -05:00
Andrew Keesler
ef74ba7238 Re-export arch diagram to embed images
I followed the steps in site/content/docs/img/README.md.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-05 08:44:10 -05:00
Andrew Keesler
b4415a05d0 I don't _think_ we need this picture anymore
See f25b4a3.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-05 08:36:26 -05:00
Margo Crawford
7817d15657 Remove image width constraint on architecture diagram 2021-01-04 17:08:47 -08:00
Margo Crawford
f25b4a3e12 De-duped architecture diagram references 2021-01-04 16:47:34 -08:00
Margo Crawford
8422659ee5 Fixed typos and issues with the demo code
- Also cleaned up some wording
2021-01-04 16:23:24 -08:00
Margo Crawford
ef828cf2e1 Add rough draft of supervisor demo
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2021-01-04 15:31:53 -05:00
Ryan Richard
546b8b5d25 Merge pull request #305 from vmware-tanzu/quiet-secrets-controllers
Sync Secret-watching controller less often by adjusting their filters to be more specific
2020-12-18 18:21:51 -08:00
Ryan Richard
a7f383f610 Merge branch 'main' into quiet-secrets-controllers 2020-12-18 18:20:54 -08:00
Ryan Richard
116c8dd6c5 SupervisorSecretsController Syncs less often by adjusting its filters
- Only watches Secrets of type
  "secrets.pinniped.dev/supervisor-csrf-signing-key"

Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-18 15:57:12 -08:00
Aram Price
1b5e8c3439 Upstream Watcher Controller Syncs less often by adjusting its filters
- Only watches Secrets of type "secrets.pinniped.dev/oidc-client"

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-18 15:41:18 -08:00
Margo Crawford
80031deab7 Merge pull request #297 from vmware-tanzu/supervisor-docs
Update docs for Supervisor
2020-12-18 15:36:00 -08:00
Margo Crawford
a005b8dce1 Merge branch 'main' into supervisor-docs 2020-12-18 15:34:34 -08:00
aram price
cc5af1a810 Fix lint error
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-18 15:28:56 -08:00
Ryan Richard
23be766c8b Move const to file-of-use and replce dup string
Signed-off-by: aram price <pricear@vmware.com>
2020-12-18 15:14:51 -08:00
Ryan Richard
2f518b8b7c TLSCertObserverController Syncs less often by adjusting its filters
- Only watches Secrets of type "kubernetes.io/tls"

Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-18 15:10:48 -08:00
Margo Crawford
6cae776e48 Change image reference on README,
Also clarified some wording between authenticators and identity providers
2020-12-18 15:09:50 -08:00
aram price
cff2dc1379 Reorder functions 2020-12-18 15:08:55 -08:00
Ryan Richard
fc250f98d0 Adjust func grouping 2020-12-18 14:58:39 -08:00
Matt Moyer
8177db3601 Merge pull request #306 from mattmoyer/website-updates
Fix a website typo and add an "Installing Pinniped" docs page.
2020-12-18 16:55:14 -06:00
Aram Price
b3e428c9de Several more controllers Sync less often by adjusting their filters
- JWKSWriterController
- JWKSObserverController
- FederationDomainSecretsController for HMAC keys
- FederationDomainSecretsController for state signature key
- FederationDomainSecretsController for state encryption key

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-18 14:55:05 -08:00
Margo Crawford
afc39cd2f7 Tweak image descriptions 2020-12-18 14:54:30 -08:00
Margo Crawford
7c9f40b6d9 Merge branch 'main' of github.com:vmware-tanzu/pinniped into supervisor-docs 2020-12-18 14:49:44 -08:00
Matt Moyer
8313ffcf7f Add "Installing Pinniped" docs page.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-18 16:44:20 -06:00
Andrew Keesler
0b12b30cb1 Updated diagrams and architecture text
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-18 14:13:29 -08:00
Matt Moyer
c27d02a929 Fix a typo on the Project Scope page.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-18 13:48:12 -06:00
Margo Crawford
4dbd8c9cae Update Concierge-only demo for v0.3.0 release
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-18 13:06:09 -05:00
Ryan Richard
1056cef384 Sync garbage collector controller less often by adjusting its filters
- Only sync on add/update of secrets in the same namespace which
  have the "storage.pinniped.dev/garbage-collect-after" annotation, and
  also during a full resync of the informer whenever secrets in the
  same namespace with that annotation exist.
- Ignore deleted secrets to avoid having this controller trigger itself
  unnecessarily when it deletes a secret. This controller is never
  interested in deleted secrets, since its only job is to delete
  existing secrets.
- No change to the self-imposed rate limit logic. That still applies
  because secrets with this annotation will be created and updated
  regularly while the system is running (not just during rare system
  configuration steps).
2020-12-18 09:36:28 -08:00
Andrew Keesler
40d93ff33b site/content/docs/architecture.md: another coat of paint with Supervisor updates
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-18 09:39:36 -05:00
Andrew Keesler
1af06bbcc9 De-dup markdown docs by deleting them in site/ tree
I'm not sure if these docs are used anywhere in our website, but I don't think
that they are. I'm assuming someone or something will yell if these should not
be deleted. These docs also live at the root of the repo, and the duplicate
versions are already drifting out of sync from one another.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-18 08:11:14 -05:00
Ryan Richard
6c210b67d4 Merge pull request #301 from vmware-tanzu/typed-secrets
Put a Type on all of the Secrets that we create in the supervisor
2020-12-17 17:42:20 -08:00
Ryan Richard
3a4405659e Merge branch 'main' into typed-secrets 2020-12-17 17:42:04 -08:00
aram price
187bd9060c All FederationDomain Secrets have distinct Types
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-17 17:07:38 -08:00
Margo Crawford
2e191084b0 Miscellaneous wording changes 2020-12-17 16:42:45 -08:00
Matt Moyer
7a98900b28 Merge pull request #302 from mattmoyer/switch-registry-references
Move our main image references to the VMware Harbor registry.
2020-12-17 18:23:12 -06:00
Margo Crawford
28e23e14b5 Demo landing page 2020-12-17 16:08:51 -08:00
Margo Crawford
5f2807e693 Updates to the architecture page. 2020-12-17 15:55:05 -08:00
Matt Moyer
e0b94f4780 Move our main image references to the VMware Harbor registry.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 17:51:09 -06:00
aram price
587cced768 Add extra type info where SecretType is used 2020-12-17 15:43:20 -08:00
Ryan Richard
50964c6677 Supervisor CSRF Secret has unique Type
Signed-off-by: aram price <pricear@vmware.com>
2020-12-17 15:30:26 -08:00
Matt Moyer
81eb0735d1 Merge pull request #299 from mattmoyer/update-go-dependencies
Update dependencies before v0.3.0 release.
2020-12-17 17:28:40 -06:00
Matt Moyer
c7931bc6d5 Remove our main module dependency on golangci-lint.
We will still pin this in CI via an image dependency.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 17:01:32 -06:00
Ryan Richard
b27e3e1a89 Put a Type on the Secrets that we create for FederationDomain JWKS
Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-17 14:48:49 -08:00
Matt Moyer
8db9331fed Update ExpectedAuthorizeCodeSessionJSONFromFuzzing.
We stared at this very carefully and we don't think there are any structural changes. Maybe something small happened to get the RNG off by one?

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 16:31:08 -06:00
Matt Moyer
3e15e184ef Update test assertions related to spf13/cobra.
It now correctly prints errors to stderr (https://github.com/spf13/cobra/pull/894).

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 16:31:08 -06:00
Matt Moyer
6a457466df Update generated k8s code for 1.19.5.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 16:31:08 -06:00
Matt Moyer
3a81fbd1b4 Update fosite error usage.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 16:31:08 -06:00
Matt Moyer
421c17c421 Update all modules.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 16:31:08 -06:00
Ryan Richard
780d236d89 Merge pull request #300 from vmware-tanzu/even-more-opc-renames
Even more "op" and "opc" local variable renames
2020-12-17 13:51:54 -08:00
Aram Price
55483b726b More "op" and "opc" local variable renames
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-17 13:49:53 -08:00
Andrew Keesler
157d041b6a README.md: first draft of Supervisor additions
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-17 15:36:33 -05:00
Ryan Richard
32602f579b Merge pull request #298 from vmware-tanzu/more-opc-rename
Rename all "op" and "opc" usages
2020-12-17 12:31:52 -08:00
Ryan Richard
65e7df1417 Merge branch 'main' into more-opc-rename 2020-12-17 12:10:19 -08:00
Ryan Richard
b96d49df0f Rename all "op" and "opc" usages
Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-17 11:34:49 -08:00
Margo Crawford
152838e998 CONTRIBUTING.md: add missing integration test dependencies
Also alphabetize dependencies because sorting wins.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-17 13:59:23 -05:00
Matt Moyer
9183c3897f Merge pull request #281 from mattmoyer/upgrade-dex
Upgrade the Dex we use for local testing to v2.27.0.
2020-12-17 12:50:36 -06:00
Andrew Keesler
b009cee877 Add Margo and Mo as maintainers of Pinniped
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-17 13:37:20 -05:00
Matt Moyer
41832369fd Upgrade the Dex we use for local testing to v2.27.0.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 12:04:09 -06:00
Matt Moyer
cc5cb394e0 Merge pull request #143 from enj/enj/i/cache_mutation_detector_unit
Enable cache mutation detector in unit tests
2020-12-17 10:09:02 -06:00
Matt Moyer
b60542f0d1 Clean this test up a trivial amount using require.Implementsf().
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-17 08:38:16 -06:00
Monis Khan
dc8e7a2f39 Enable cache mutation detector in unit tests
Signed-off-by: Monis Khan <mok@vmware.com>
2020-12-17 08:38:15 -06:00
Matt Moyer
34e6e7567f Merge pull request #295 from ankeesler/fix-secret-status
Only set single secret status field in FederationDomainSecretsController
2020-12-17 08:26:23 -06:00
Andrew Keesler
04d54e622a Only set single secret status field in FederationDomainSecretsController
This implementation is janky because I wanted to make the smallest change
possible to try to get the code back to stable so we can release.

Also deep copy an object so we aren't mutating the cache.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-17 07:41:53 -05:00
Ryan Richard
4c6e1e5fb3 supervisor_login_test.go: wait for the /jwks.json endpoint to be ready
- Also fail in a more obvious way if the token exchanged failed by
  adding an assertion about its status code
2020-12-16 17:59:39 -08:00
Ryan Richard
b2b906f4fe supervisor_discovery_test.go: make test timeouts longer to avoid flakes 2020-12-16 15:13:02 -08:00
Margo Crawford
40586b255c Merge pull request #293 from vmware-tanzu/rename-oidcprovider-and-upstreamoidcprovider
Rename OIDCProvider -> FederationDomain and UpstreamOIDCProvider -> OIDCIdentityProvider
2020-12-16 14:58:33 -08:00
Margo Crawford
196e43aa48 Rename off of main
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-16 14:27:09 -08:00
Matt Moyer
fbe1a202c2 Merge pull request #283 from vmware-tanzu/username-and-subject-claims
Adjust subject and username claims
2020-12-16 15:23:34 -06:00
Matt Moyer
7dae166a69 Merge branch 'main' into username-and-subject-claims 2020-12-16 15:23:19 -06:00
Matt Moyer
72ce69410e Merge pull request #273 from vmware-tanzu/secret-generation
Generate secrets for Pinniped Supervisor
2020-12-16 15:22:23 -06:00
Matt Moyer
7bb0d649c0 Merge pull request #290 from mattmoyer/rename-token-exchange-scope
Rename the "pinniped.sts.unrestricted" scope to "pinniped:request-audience".
2020-12-16 15:22:05 -06:00
Matt Moyer
c110e173ac Merge pull request #286 from mattmoyer/upgrade-debian-base-image
Upgrade base images to Debian 10.7-slim.
2020-12-16 15:21:31 -06:00
Matt Moyer
111f6513ac Upgrade base images to Debian 10.7-slim.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 15:16:31 -06:00
Matt Moyer
5367fd9fcb Trigger CI 2020-12-16 15:13:28 -06:00
Andrew Keesler
095ba14cc8 Merge remote-tracking branch 'upstream/main' into secret-generation 2020-12-16 15:40:34 -05:00
Andrew Keesler
446863ad96 Merge pull request #292 from ankeesler/golang-debian-bump
Upgrade golang (1.15.5 -> 1.15.6)
2020-12-16 15:38:12 -05:00
Matt Moyer
8527c363bb Rename the "pinniped.sts.unrestricted" scope to "pinniped:request-audience".
This is a bit more clear. We're changing this now because it is a non-backwards-compatible change that we can make now since none of this RFC8693 token exchange stuff has been released yet.

There is also a small typo fix in some flag usages (s/RF8693/RFC8693/)

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 14:24:13 -06:00
Matt Moyer
05127f4cfb Merge pull request #291 from mattmoyer/tweak-oidcclient-timeouts
Tweak timeouts in oidcclient package.
2020-12-16 14:23:32 -06:00
Ryan Richard
653224c2ad types_jwt.go.tmpl: Replace spaces with tabs 2020-12-16 12:21:30 -08:00
Margo Crawford
406fc95501 Empty commit to trigger CI
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-16 11:49:59 -08:00
Matt Moyer
01b6bf7850 Tweak timeouts in oidcclient package.
- The overall timeout for logins is increased to 90 minutes.
- The timeout for token refresh is increased from 30 seconds to 60 seconds to be a bit more tolerant of extremely slow networks.
- A new, matching timeout of 60 seconds has been added for the OIDC discovery, auth code exchange, and RFC8693 token exchange operations.

The new code uses the `http.Client.Timeout` field rather than managing contexts on individual requests. This is easier because the OIDC package stores a context at creation time and tries to use it later when performing key refresh operations.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 13:47:08 -06:00
Matt Moyer
2840e4e152 Merge pull request #288 from mattmoyer/fixup-securityheaders
Fix a regression in securityheaders package and add tests.
2020-12-16 13:46:28 -06:00
Matt Moyer
3948bb76d8 Be more lax in some of our test assertions.
Fosite overrides the `Cache-Control` header we set, which is basically fine even though it's not exactly what we want.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 13:15:38 -06:00
Matt Moyer
24c01d3e54 Add an integration test to verify security headers on the supervisor authorize endpoint.
It would be great to do this for the supervisor's callback endpoint as well, but it's difficult to get at those since the request happens inside the spawned browser.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 12:41:06 -06:00
Matt Moyer
74e52187a3 Simplify securityheader package by merging header fields.
From RFC2616 (https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2):
 > It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair,
 > without changing the semantics of the message, by appending each subsequent field-value to the first,
 > each separated by a comma.

This was correct before, but this simplifes a bit and shaves off a few bytes from the response.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 12:41:05 -06:00
Matt Moyer
602f3c59ba Fix a regression in securityheader package.
The bug itself has to do with when headers are streamed to the client. Once a wrapped handler has sent any bytes to the `http.ResponseWriter`, the value of the map returned from `w.Header()` no longer matters for the response. The fix is fairly trivial, which is to add those response headers before invoking the wrapped handler.

The existing unit test didn't catch this due to limitations in `httptest.NewRecorder()`. It is now replaced with a new test that runs a full HTTP test server, which catches the previous bug.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-16 12:41:05 -06:00
Aram Price
a33dace80b Upgrade golang (1.15.5 -> 1.15.6)
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-16 13:31:54 -05:00
Margo Crawford
1d4012cabf jwtcachefiller_test.go: don't assert about time zones in errors
Because the library that we are using which returns that error
formats the timestamp in localtime, which is LMT when running
on a laptop, but is UTC when running in CI.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-16 10:17:17 -08:00
Ryan Richard
dcb19150fc Nest claim configs one level deeper in JWTAuthenticatorSpec
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-16 09:42:19 -08:00
Matt Moyer
bc1dc0805e Merge pull request #289 from mattmoyer/fix-secret-type-doc-comment
Fix documentation comment for the UpstreamOIDCProvider's spec.client.secretName type.
2020-12-16 10:09:05 -06:00
Andrew Keesler
fec80113c7 Revert "Retry a couple of times if we fail to get a token from the Supervisor"
This reverts commit be4e34d0c0.

Roll back this change that was supposed to make the test more robust. If we
retry multiple token exchanges with the same auth code, of course we are going
to get failures on the second try onwards because the auth code was invalidated
on the first try.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-16 09:04:29 -05:00
Andrew Keesler
5bdbfe1bc6 test/integration: more verbosity to try to track down flakes...
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-16 09:04:25 -05:00
Matt Moyer
404ff93102 Fix documentation comment for the UpstreamOIDCProvider's spec.client.secretName type.
The value is correctly validated as `secrets.pinniped.dev/oidc-client` elsewhere, only this comment was wrong.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 21:52:07 -06:00
aram price
78df80f128 Tests ensure OIDCProvider secrets exist
... whenever one is successfully created.
2020-12-15 18:26:27 -08:00
Ryan Richard
40c6a67631 Merge branch 'main' into username-and-subject-claims 2020-12-15 18:09:44 -08:00
Ryan Richard
91af51d38e Fix integration tests to work with the username and sub claims
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-15 17:16:08 -08:00
Margo Crawford
a10d219049 Pass through custom groups claim and username claim
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-15 16:11:53 -08:00
Andrew Keesler
0758ecfea8 Tests wait for OIDCProvider secrets to be set
Signed-off-by: aram price <pricear@vmware.com>
2020-12-15 15:46:55 -08:00
Ryan Richard
05ab8f375e Default to "username" claim in jwtcachefiller
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-15 14:37:38 -08:00
Aram Price
0bd428e45d test/integration: more logging to track down flakes
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 16:52:57 -05:00
Margo Crawford
720bc7ae42 jwtcachefiller_test.go: refactor and remove "if short skip" check
- Refactor the test to avoid testing a private method and instead
  always test the results of running the controller.
- Also remove the `if testing.Short()` check because it will always
  be short when running unit tests. This prevented the unit test
  from ever running, both locally and in CI.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-15 13:33:49 -08:00
Andrew Keesler
056afc17bd Merge remote-tracking branch 'upstream/main' into secret-generation 2020-12-15 15:55:46 -05:00
Andrew Keesler
35bb76ea82 Ensure labels are set correct on generated Supervisor secret
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 15:55:14 -05:00
Andrew Keesler
3d4717b772 Merge pull request #285 from vmware-tanzu/log-unexpected-upstream
Log when unexpected Upstream OIDC Providers found
2020-12-15 15:30:20 -05:00
Andrew Keesler
2b7685fa23 Merge branch 'main' into log-unexpected-upstream 2020-12-15 15:30:05 -05:00
Andrew Keesler
9d9040944a Secrets owned by Deployment have Controller: false
- This is to prevent K8s internal Deployment controller from trying to
manage these objects

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 12:12:47 -08:00
Matt Moyer
2b2f1bbfc9 Merge pull request #276 from mattmoyer/extended-e2e-cli
Enhance CLI to integrate supervisor and concierge capabilities.
2020-12-15 13:23:51 -06:00
aram price
2edcdc92f4 Log when unexpected Upstream OIDC Providers found
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 10:49:13 -08:00
Ryan Richard
0e60c93cef Add UsernameClaim and GroupsClaim to JWTAuthenticator CRD spec
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-15 10:36:19 -08:00
Matt Moyer
0b38d6c763 Add TestE2EFullIntegration test which combines supervisor, concierge, and CLI.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:50 -06:00
Matt Moyer
ff49647de4 Add some missing test logs in test/library/client.go.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:50 -06:00
Matt Moyer
e0eba9d5a6 Refactor library.CreateTestJWTAuthenticator() so we can also use the supervisor as an upstream.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:50 -06:00
Matt Moyer
5ad3c65ae1 Close the right pipe output in runPinnipedLoginOIDC.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:50 -06:00
Matt Moyer
aca9af748b Cleanup TestSuccessfulCredentialRequest and TestCLILoginOIDC a little.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:49 -06:00
Matt Moyer
8cdcb89cef Add a library.PinnipedCLIPath() test helper, with caching.
Caching saves us a little bit of time now that we're using the CLI in more and more tests.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:49 -06:00
Matt Moyer
70fd330178 Add library.CreateTestClusterRoleBinding test helper.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:49 -06:00
Matt Moyer
ad5e257600 Add a library.RandHex() test helper.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:34:49 -06:00
Matt Moyer
4088793cc5 Add a .ProxyEnv() helper on the test environment.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:04 -06:00
Matt Moyer
b6edc3dc08 Replace TestCLIGetKubeconfig with TestCLIGetKubeconfigStaticToken.
It now tests both the deprecated `pinniped get-kubeconfig` and the new `pinniped get kubeconfig --static-token` flows.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:03 -06:00
Matt Moyer
fe4e2d620d Update TestCLIGetKubeconfig to ignore stderr output from get-kubeconfig.
This will now have a deprecation warning, so we can't treat is as part of the YAML output.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:03 -06:00
Matt Moyer
f9691208d5 Add library.NewRestConfigFromKubeconfig() test helper.
This is extracted from library.NewClientsetForKubeConfig(). It is useful so you can assert properties of the loaded, parsed kubeconfig.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:03 -06:00
Matt Moyer
71850419c1 Overhaul pinniped CLI subcommands.
- Adds two new subcommands: `pinniped get kubeconfig` and `pinniped login static`
- Adds concierge support to `pinniped login oidc`.
- Adds back wrapper commands for the now deprecated `pinniped get-kubeconfig` and `pinniped exchange-credential` commands. These now wrap `pinniped get kubeconfig` and `pinniped login static` respectively.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:03 -06:00
Matt Moyer
dfbb5b60de Remove pinniped get-kubeconfig CLI subcommand.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:03 -06:00
Matt Moyer
3b5f00439c Remove pinniped exchange-credential CLI subcommand.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:02 -06:00
Matt Moyer
9b7fe01648 Add a new ./pkg/conciergeclient package to replace ./internal/client.
This is a slighly evolved version of our previous client package, exported to be public and refactored to use functional options for API maintainability.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-15 12:28:02 -06:00
Andrew Keesler
2e784e006c Merge remote-tracking branch 'upstream/main' into secret-generation 2020-12-15 13:24:33 -05:00
Andrew Keesler
08cf2f7cd1 Merge pull request #284 from ankeesler/oidcprovider-enum-values
SameIssuerHostMustUseSameSecret is a valid OIDCProvider status, and help some test flakes
2020-12-15 13:23:16 -05:00
Andrew Keesler
be4e34d0c0 Retry a couple of times if we fail to get a token from the Supervisor
I hope this will make TestSupervisorLogin less flaky. There are some instances
where the front half of the OIDC login flow happens so fast that the JWKS
controller doesn't have time to properly generate an asymmetric key.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 11:53:58 -05:00
Andrew Keesler
50f9b434e7 SameIssuerHostMustUseSameSecret is a valid OIDCProvider status
I saw this message in our CI logs, which led me to this fix.
  could not update status: OIDCProvider.config.supervisor.pinniped.dev "acceptance-provider" is invalid: status.status: Unsupported value: "SameIssuerHostMustUseSameSecret": supported values: "Success", "Duplicate", "Invalid"

Also - correct an integration test error message that was misleading.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 11:53:53 -05:00
Ryan Richard
43bb7117b7 Allow upstream group claim values to be either arrays or strings 2020-12-15 08:34:24 -08:00
Andrew Keesler
7320928235 Get rid of TODOs in code by punting on them
We will do these later; they have been recorded in a work tracking record.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 09:58:46 -05:00
Andrew Keesler
d2498c96e0 Merge remote-tracking branch 'upstream/main' into secret-generation 2020-12-15 09:27:23 -05:00
Andrew Keesler
82ae98d9d0 Set secret names on OIDCProvider status field
We believe this API is more forwards compatible with future secrets management
use cases. The implementation is a cry for help, but I was trying to follow the
previously established pattern of encapsulating the secret generation
functionality to a single group of packages.

This commit makes a breaking change to the current OIDCProvider API, but that
OIDCProvider API was added after the latest release, so it is technically still
in development until we release, and therefore we can continue to thrash on it.

I also took this opportunity to make some things private that didn't need to be
public.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 09:13:01 -05:00
Andrew Keesler
60d4a7beac Test more filters in SupervisorSecretsController (see 6e8d564013)
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 07:58:33 -05:00
Andrew Keesler
9a3e60d4df go.mod: unnecessary dependency slipped in (c3f73ff)
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-15 07:56:31 -05:00
aram price
e03e344dcd SecretHelper depends less on OIDCProvider
This should allow the helper to be more generic so that it can be used
with the SupervisorSecretsController
2020-12-14 19:35:45 -08:00
aram price
bf86bc3383 Rename for clarity 2020-12-14 18:36:56 -08:00
Ryan Richard
16dfab0aff token_handler_test.go: Add tests for username and groups custom claims 2020-12-14 18:27:14 -08:00
aram price
b799515f84 Pull symmetricsecrethelper package up to generator
- rename symmetricsecrethelper.New => generator.NewSymmetricSecretHelper
2020-12-14 17:41:02 -08:00
Ryan Richard
417e6b1fee Merge pull request #282 from vmware-tanzu/security-headers
Add Cache-Control, Pragma, Expires, and X-DNS-Prefetch-Control headers
2020-12-14 17:22:09 -08:00
Margo Crawford
afcd5e3e36 WIP: Adjust subject and username claims
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-14 17:05:53 -08:00
aram price
b1ee434ddf Rename in preparation for refactor 2020-12-14 16:44:27 -08:00
aram price
6e8d564013 Test filters in SupervisorSecretsController 2020-12-14 16:08:48 -08:00
Ryan Richard
16907e4453 Add Cache-Control, Pragma, Expires, and X-DNS-Prefetch-Control headers
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-14 15:28:32 -08:00
Andrew Keesler
9c79adcb26 Rename and move some code to perpare for refactor
Signed-off-by: aram price <pricear@vmware.com>
2020-12-14 14:24:13 -08:00
Aram Price
5b7a86ecc1 Integration test for Supervisor secret controllers
This forced us to add labels to the CSRF cookie secret, just as we do
for other Supervisor secrets. Yay tests.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-14 15:53:12 -05:00
Andrew Keesler
cae0023234 Merge remote-tracking branch 'upstream/main' into secret-generation
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-14 11:44:01 -05:00
Andrew Keesler
2f28d2a96b Synchronize the OIDCProvider secrets cache
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-14 11:32:33 -05:00
Andrew Keesler
e3ea141bf3 Reuse helper filter in generic secret gen controller
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-14 10:37:27 -05:00
Andrew Keesler
b043dae149 Finish first implementation of generic secret generator controller
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-14 10:36:45 -05:00
aram price
3ca877f1df WIP - preliminary OIDCProviderSecrets controller
Tests not yet passing, controller is incomplete and expectations may be
incorrect.
2020-12-13 17:37:49 -05:00
aram price
3e31668eb0 Refactor some utilitiy methods for sharing. 2020-12-13 17:37:48 -05:00
aram price
9e2213cbae Rename for clarity
- makes space for OIDCPrivder related controller
2020-12-13 17:37:48 -05:00
Ryan Richard
a5c07042c1 Merge pull request #279 from vmware-tanzu/fosite-settings
Update more fosite settings
2020-12-11 18:19:50 -08:00
Ryan Richard
7cda6628a6 Merge branch 'main' into fosite-settings 2020-12-11 18:19:37 -08:00
Ryan Richard
020fbcf190 Adjust some expectations about the state and nonce lengths 2020-12-11 17:39:58 -08:00
Ryan Richard
791c50fd33 Merge pull request #278 from vmware-tanzu/fosite-storage-gc
Garbage collect the fosite secrets to limit amount of storage used
2020-12-11 17:17:15 -08:00
Margo Crawford
2a19dd0d2e Pass prompt through to upstream login request
Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-11 17:13:27 -08:00
Margo Crawford
ded28dff15 Update the fosite settings
- AudienceMatchingStrategy: we want to use the default matcher from
  fosite, so remove that line
- AllowedPromptValues: We can use the default if we add a small
  change to the auth_handler.go to account for it (in a future commit)
- MinParameterEntropy: Use the fosite default to make it more likely
  that off the shelf OIDC clients can work with the supervisor

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-11 16:15:50 -08:00
Ryan Richard
baa1a4a2fc Supervisor storage garbage collection controller enabled in production
- Also add more log statements to the controller
- Also have the controller apply a rate limit to itself, to avoid
  having a very chatty controller that runs way more often than is
  needed.
- Also add an integration test for the controller's behavior.

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-11 15:21:34 -08:00
Andrew Keesler
022dcd1909 Update secretgenerator controller after synchronous review
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 15:37:10 -05:00
Andrew Keesler
e2aad48852 internal/oidc/dynamiccodec: loosen test to reduce flakes
When we try to decode with the wrong decryption key, we could get any number of
error messages, depending on what failure mode we are in (couldn't authenticate
plaintext after decryption, couldn't deserialize, etc.). This change makes the
test weaker, but at least we know we will get an error message in the case where
the decryption key is wrong.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 11:49:27 -05:00
Andrew Keesler
e17bc31b29 Pass CSRF cookie signing key from controller to cache
This also sets the CSRF cookie Secret's OwnerReference to the Pod's grandparent
Deployment so that when the Deployment is cleaned up, then the Secret is as
well.

Obviously this controller implementation has a lot of issues, but it will at
least get us started.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 11:49:27 -05:00
Andrew Keesler
22c5b102ed internal/downward: add support for (optional) pod name
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 11:49:27 -05:00
Andrew Keesler
0246e57d7f Set lifespans on state and CSRF cooking encoding
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 11:49:22 -05:00
Andrew Keesler
9460b08873 Use just-in-time HMAC signing key fetching in our Fosite config
This pattern is similar to what we did in
58237d0e7d.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-11 11:16:46 -05:00
Margo Crawford
ed9b3ffce5 Add controller for garbage collecting secrets
Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-10 17:34:05 -08:00
aram price
a3285fc187 Fix variable / package name collision 2020-12-10 17:32:55 -08:00
aram price
e1173eb5eb manager.Manager is initialized with secret.Cache
- hard-coded secret.Cache is passed in from pinniped-supervisor/main
2020-12-10 17:32:55 -08:00
aram price
72bc458c8e Manager uses secret.Cach with hardcoded values 2020-12-10 17:32:55 -08:00
Andrew Keesler
e067892ffc Add secret.Cache to hold crypto inputs 2020-12-10 17:32:55 -08:00
aram price
2f87be3f94 Manager uses dynamiccodec.Codec for cookie encoding
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-10 17:32:55 -08:00
Andrew Keesler
1291380611 dynamiccodec.Codec uses securecookie.JSONEncoder
Signed-off-by: aram price <pricear@vmware.com>
2020-12-10 17:32:55 -08:00
aram price
ccac124b7a Fix broken test
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-10 17:32:55 -08:00
Andrew Keesler
d8212d1337 Whitespace
Signed-off-by: aram price <pricear@vmware.com>
2020-12-10 17:32:55 -08:00
aram price
030edaf72d KeyFunc no longer uses multi-value return
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-10 17:32:55 -08:00
Andrew Keesler
c3f73ffb57 Check in some musings on a symmetric key generator controller
There is still a test failing, but I am sure it is a simple fix hiding in the
code. I think this is the general shape of the controller that we want.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-10 17:32:55 -08:00
Andrew Keesler
3e112fb1ac internal/oidc/dynamiccodec: first draft
Note that we don't cache the securecookie.SecureCookie that we use in our
implementation. This was purely because of laziness. We should think about
caching this value in the future.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-10 17:32:55 -08:00
Ryan Richard
afd216308b KubeStorage annotates every Secret with garbage-collect-after timestamp
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-10 14:47:58 -08:00
Margo Crawford
b0c354637d WIP passing lifetime through to storage, unit tests are failing
Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-10 12:15:40 -08:00
Ryan Richard
c001bb876e Merge pull request #275 from vmware-tanzu/fosite-storage-gc-prefactor
Fosite storage garbage collection prefactor
2020-12-10 10:50:29 -08:00
Ryan Richard
3c6d1a1924 Merge branch 'main' into fosite-storage-gc 2020-12-10 10:45:26 -08:00
Margo Crawford
6f40dcb471 Increase the RefreshTokenSessionStorageLifetime
- Make it more likely that the end user will get the more specific error
  message saying that their refresh token has expired the first time
  that they try to use an expired refresh token

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-10 10:44:27 -08:00
Ryan Richard
a561fd21d9 Consolidate the supervisor's timeout settings into a single struct
- This struct represents the configuration of all timeouts. These
  timeouts are all interrelated to declare them all in one place.
  This should also make it easier to allow the user to override
  our defaults if we would like to implement such a feature in the
  future.

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-10 10:14:54 -08:00
Matt Moyer
40c9e8472c Merge pull request #272 from mattmoyer/default-cli-scopes
Tweak default CLI `--scopes` parameter to match supervisor use case.
2020-12-10 11:41:22 -06:00
Matt Moyer
e7338da3dc Tweak default CLI --scopes parameter to match supervisor use case.
This should be a better default for most cases.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-10 10:48:11 -06:00
Matt Moyer
0c52739997 Merge pull request #271 from mattmoyer/fix-cli-content-type-parsing
Fix bug in handling response content-type in oidcclient.
2020-12-10 10:46:10 -06:00
Matt Moyer
9d3c98232b Fix bug in handling response content-type in oidcclient.
Before this, we weren't properly parsing the `Content-Type` header. This breaks in integration with the Supervisor since it sends an extra encoding parameter like `application/json;charset=UTF-8`.

This change switches to properly parsing with the `mime.ParseMediaType` function, and adds test cases to match the supervisor behavior.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-10 10:12:56 -06:00
Matt Moyer
5a0918afde Merge pull request #270 from mattmoyer/default-cli-client-id
Add a default --client-id in `pinniped login oidc` command.
2020-12-10 10:12:28 -06:00
Matt Moyer
4395d5a0ca Add a default --client-id in pinniped login oidc command.
This default matches the static client we have defined in the supervisor, which will be the correct value in most cases.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-10 09:46:07 -06:00
Andrew Keesler
d83927ae75 Merge pull request #268 from vmware-tanzu/secret-generation-prefactor
Secret generation prefactor
2020-12-10 08:39:32 -05:00
aram price
86c75b7a80 CSRF cookie is no longer encrypted 2020-12-09 17:34:02 -08:00
aram price
f1f8ffa456 Distinct Encoder's use distinct keys 2020-12-09 17:34:02 -08:00
aram price
4a5f8e30a8 Use distinct Encoder for state and csrf data 2020-12-09 17:34:02 -08:00
aram price
e111ca02da Use the narrowest possible interface 2020-12-09 17:34:02 -08:00
aram price
6ec3589112 Use recorder Cookies() helper
- replaces hand-parsing of cookie strings
2020-12-09 17:34:02 -08:00
Margo Crawford
2ddba8d825 Merge pull request #267 from vmware-tanzu/token-exchange-endpoint
Implement RFC8693 token exchange handler in the supervisor
2020-12-09 17:13:28 -08:00
Margo Crawford
218f27306c Integration test for refresh grant
Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-09 17:07:37 -08:00
Margo Crawford
fde2e6fa97 Merge remote-tracking branch 'origin/main' into token-exchange-endpoint 2020-12-09 15:22:54 -08:00
Ryan Richard
4d82ec1283 Merge pull request #262 from vmware-tanzu/token-refresh
Support for the refresh grant in the supervisor's token endpoint
2020-12-09 15:22:02 -08:00
Ryan Richard
5b7c510577 Fixed error handling for token exchange when openid scope missing
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-09 15:15:50 -08:00
Ryan Richard
0abadddb1a token_handler_test.go: modify a test about refresh request scopes param
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-09 15:03:52 -08:00
Margo Crawford
5f6e7de785 Merge branch 'token-refresh' into token-exchange-endpoint
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-09 14:56:41 -08:00
Ryan Richard
64631d5780 token_handler_test.go: add even more test cases for refresh grant
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-09 14:53:39 -08:00
Ryan Richard
0386658d26 token_handler_test.go: add more test cases for refresh grant 2020-12-09 14:12:00 -08:00
Matt Moyer
167d440b65 Remove this unneccesary go113 nolint directives.
We disabled this linter across the project.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 14:51:27 -06:00
Matt Moyer
3e6ebab389 Clean up TestTokenExchange a bit.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 14:49:44 -06:00
Matt Moyer
f90b5d48de Merge branch 'token-refresh' of github.com:vmware-tanzu/pinniped into token-exchange-endpoint 2020-12-09 14:46:57 -06:00
Matt Moyer
016b0e9a8e Satisfy the pedantic linter config 🙃.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 14:41:27 -06:00
Ryan Richard
51c828382f Supervisor token endpoint supports refresh grant type
- This commit does not include the sad path tests for the refresh
  grant type, which will come in a future commit.
2020-12-09 12:12:59 -08:00
Matt Moyer
02d96d731f Finish TestTokenExchange unit tests and add missing scope check.
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-09 13:56:53 -06:00
Ryan Richard
cac3a3520f Merge branch 'main' into token-refresh 2020-12-09 09:58:21 -08:00
Matt Moyer
b04db6ad2b Fix some false positive gosec warnings.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 10:42:37 -06:00
Matt Moyer
f1aff2faab Start extending TestSupervisorLogin to test the token exchange flow (WIP).
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 10:23:10 -06:00
Matt Moyer
b1542be7b1 In oidcclient token exchange request, pass client_id but don't bother with authorization header.
I think this should be more correct. In the server we're authenticating the request primarily via the `subject_token` parameter anyway, and Fosite needs the `client_id` to be set.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 10:08:41 -06:00
Matt Moyer
1db2ae3a45 Add more parameter validations and refactor internal/oidc/token_exchange.go.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 10:04:58 -06:00
Matt Moyer
e25d090ca9 Merge branch 'main' of github.com:vmware-tanzu/pinniped into token-exchange-endpoint 2020-12-09 10:00:54 -06:00
Andrew Keesler
5f4348c57d Merge pull request #266 from ankeesler/fix-jwt-auth-ca-bundle
Fix `JWTAuthenticator` CA bundle
2020-12-09 10:43:33 -05:00
Matt Moyer
644cb687b9 Grant the Pinniped STS scope in authorize/callback handlers.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 09:36:45 -06:00
Matt Moyer
bebe25c32e Merge branch 'main' of github.com:vmware-tanzu/pinniped into token-exchange-endpoint 2020-12-09 09:25:58 -06:00
Andrew Keesler
4c0fb12cf6 test/integration: only set JWTAuthenticator CA bundle when it exists
See comment in code.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-09 10:15:53 -05:00
Andrew Keesler
93cfd8c93a Fix prepare-for-integration-tests.sh and Tiltfile for kubectl 1.20
kubectl 1.20 prints "Kubernetes control plane" instead of "Kubernetes master".

Signed-off-by: Andrew Keesler <akeesler@vmware.com>

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-09 10:15:34 -05:00
Matt Moyer
5f1bd5ec31 Update TestNullStorage_GetClient with adjusted pinniped-cli scopes.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-09 09:12:32 -06:00
Andrew Keesler
8fcc176d8b Merge pull request #258 from ankeesler/jwt-authenticator
Add JWTAuthenticator API and initial controller
2020-12-09 08:21:04 -05:00
Ryan Richard
6420caca94 Bring back the test that was skipped by the previous commit
- This test is still a work in progress. Some TODO comments
  have been added to give hints for next steps.
2020-12-08 18:25:01 -08:00
Ryan Richard
f84dda937b Merge branch 'token-refresh' into token-exchange-endpoint 2020-12-08 18:12:12 -08:00
Ryan Richard
ef4ef583dc token_handler_test.go: Refactor how we specify the expected results
- This is to make it easier for the token exchange branch to also edit
  this test without causing a lot of merge conflicts with the
  refresh token branch, to enable parallel development of closely
  related stories.
2020-12-08 18:10:55 -08:00
Margo Crawford
f103c02408 Add check for grant type in tokenexchangehandler,
- also started writing a test for the tokenexchangehandler, skipping for
now

Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-08 17:33:08 -08:00
Margo Crawford
ef3f837800 Merge remote-tracking branch 'origin/token-refresh' into token-exchange-endpoint 2020-12-08 16:58:35 -08:00
Ryan Richard
170982a688 refactor token_handler_test.go: easier to make more requests after initial authcode exchange
- This refactor will allow us to add new test tables for the
  refresh and token exchange requests, which both must come after
  an initial successful authcode exchange has already happened

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-08 16:54:58 -08:00
Margo Crawford
a852baac75 Merge remote-tracking branch 'origin/token-refresh' into token-exchange-endpoint
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-08 12:55:44 -08:00
Andrew Keesler
381a2e749a impotent -> idempotent
These words do not mean the same thing...

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:49 -05:00
Aram Price
9ed5dcb031 Only create underlying jwt authenticator when spec has changed
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:49 -05:00
Andrew Keesler
e0ee18a993 Always close JWTAuthenticator underlying authenticator
Otherwise we will leak goroutines.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:48 -05:00
Andrew Keesler
0efc19a1b7 Support JWTAuthenticator in pinniped CLI
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:48 -05:00
Andrew Keesler
57103e0a9f Add JWTAuthenticator controller
See https://github.com/vmware-tanzu/pinniped/issues/260 for UX bummer.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:48 -05:00
Andrew Keesler
946b0539d2 Add JWTAuthenticator API type
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-08 15:41:48 -05:00
Ryan Richard
a9111f39af Merge branch 'main' into token-refresh 2020-12-08 12:32:41 -08:00
Ryan Richard
18d90a727e token_handler_test.go: refresh token gets deleted when authcode reused 2020-12-08 12:12:55 -08:00
Ryan Richard
c090eb6a62 Supervisor token endpoint returns refresh tokens when requested 2020-12-08 11:47:39 -08:00
Andrew Keesler
8f51993db2 Merge pull request #265 from vmware-tanzu/scope-constants
Use constants for scope values
2020-12-08 14:32:09 -05:00
aram price
8d2b8ae6b5 Use constants for scope values 2020-12-08 10:46:05 -08:00
Matt Moyer
afbef23a51 WIP implementing TokenExchangeHandler methods
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-08 10:17:03 -08:00
Margo Crawford
e5ecaf01a0 WIP stubbing out tokenexchangehandler 2020-12-08 09:28:19 -08:00
Margo Crawford
b7b6816531 Merge pull request #259 from mattmoyer/add-cli-request-audience
Add a `--request-audience` flag to the `pinniped login oidc` CLI command
2020-12-08 09:26:19 -08:00
Matt Moyer
bfcd2569e9 Add a --request-audience flag to the pinniped login oidc CLI command.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-08 10:22:20 -06:00
Aram Price
d91baba240 authorize and callback endpoints now handle the offline_access scope
- This is in preparation for the token endpoint to support the refresh
  grant

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-07 17:22:34 -08:00
Ryan Richard
6a90a10123 Merge pull request #249 from vmware-tanzu/token-endpoint
OIDC token endpoint supports authcode flow
2020-12-07 15:08:07 -08:00
Ryan Richard
12e5f94e75 Merge branch 'main' into token-endpoint 2020-12-07 14:23:40 -08:00
Ryan Richard
e1ae48f2e4 Discovery does not return token_endpoint_auth_signing_alg_values_supported
`token_endpoint_auth_signing_alg_values_supported` is only related to
private_key_jwt and client_secret_jwt client authentication methods
at the token endpoint, which we do not support. See
https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
for more details.

Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-07 14:15:31 -08:00
Matt Moyer
dcaf9166dc Merge pull request #261 from mattmoyer/remove-goerr113-linter
Disable the goerr113 linter.
2020-12-07 16:07:11 -06:00
Matt Moyer
9e945d7547 Disable the goerr113 linter.
This linter is nice in principle, but I've found it more annoying than helpful in practice.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-07 15:53:41 -06:00
Aram Price
648fa4b9ba Backfill test for token endpoint error when JWK is not yet available
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-07 11:53:24 -08:00
Ryan Richard
e0b6133bf1 Integration tests call supervisor token endpoint and validate response
Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-04 17:07:04 -08:00
Aram Price
ac19782405 Merge branch 'main' into token-endpoint
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-04 15:52:49 -08:00
Ryan Richard
858356610c Make assertions about how many secrets were stored by fosite in tests
In both callback_handler_test.go and token_handler_test.go

Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-04 15:40:17 -08:00
Matt Moyer
040ad3293a Merge pull request #255 from mattmoyer/reduce-default-cli-scopes
Remove "email" and "profile" from default scopes requested by CLI.
2020-12-04 17:04:03 -06:00
Matt Moyer
66270fded0 Merge pull request #257 from mattmoyer/prefactoring-for-cli-request-audience
Prefactor before adding CLI "request audience" functionality.
2020-12-04 17:03:38 -06:00
Aram Price
26a8747509 Use the more specific label name of "storage.pinniped.dev/type"
Instead of the less specific "storage.pinniped.dev"

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-04 14:39:11 -08:00
Ryan Richard
ac83633888 Add fosite kube storage for access and refresh tokens
Also switched the token_handler_test.go to use kube storage.

Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-04 14:31:06 -08:00
Matt Moyer
c6ead9d7dd Remove "email" and "profile" from default scopes requested by CLI.
We decided that we don't really need these in every case, since we'll be returning username and groups in a custom claim.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-04 16:02:16 -06:00
Matt Moyer
8c3be3ffb2 Refactor UpstreamOIDCIdentityProviderI claim handling.
This refactors the `UpstreamOIDCIdentityProviderI` interface and its implementations to pass ID token claims through a `*oidctypes.Token` return parameter rather than as a third return parameter.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-04 15:35:35 -06:00
Matt Moyer
014d760f3d Add validated ID token claims to the oidctypes.Token structure.
This is just a more convenient copy of these values which are already stored inside the ID token. This will save us from having to pass them around seprately or re-parse them later.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-04 15:18:41 -06:00
Andrew Keesler
8d5f4a93ed Get rid of an unnecessary comment from 58237d0e7d
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-04 11:16:32 -05:00
Andrew Keesler
37631b41ea Don't set our TokenURL - we don't need it right now
TokenURL is used by Fosite to validate clients authenticating with the
private_key_jwt method. We don't have any use for this right now, so just leave
this blank until we need it.

See when Ryan brought this up in
https://github.com/vmware-tanzu/pinniped/pull/239#discussion_r528022162.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-04 10:18:45 -05:00
Andrew Keesler
03806629b8 Cleanup code via TODOs accumulated during token endpoint work
We opened https://github.com/vmware-tanzu/pinniped/issues/254 for the TODO in
dynamicOpenIDConnectECDSAStrategy.GenerateToken().

This commit also ensures that linting and unit tests are passing again.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-04 10:09:42 -05:00
Andrew Keesler
83e0934864 Add logging in dynamic OIDC ECDSA strategy
I'm worried that these errors are going to be really burried from the user, so
add some log statements to try to make them a tiny bit more observable.

Also follow some of our error message convetions by using lowercase error
messages.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-04 09:05:39 -05:00
Andrew Keesler
2dc3ab1840 Merge remote-tracking branch 'upstream/main' into token-endpoint 2020-12-04 08:58:18 -05:00
Matt Moyer
7b088d611d Merge pull request #252 from mattmoyer/fix-csrf-cookie-same-site
Switch CSRF cookie from `Same-Site=Strict` to `Same-Site=Lax`.
2020-12-03 21:53:24 -06:00
Matt Moyer
f0ebd808d7 Switch CSRF cookie from Same-Site=Strict to Same-Site=Lax.
This CSRF cookie needs to be included on the request to the callback endpoint triggered by the redirect from the OIDC upstream provider. This is not allowed by `Same-Site=Strict` but is allowed by `Same-Site=Lax` because it is a "cross-site top-level navigation" [1].

We didn't catch this earlier with our Dex-based tests because the upstream and downstream issuers were on the same parent domain `*.svc.cluster.local` so the cookie was allowed even with `Strict` mode.

[1]: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00#section-3.2

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 21:30:00 -06:00
Margo Crawford
0bb2b10b3b Passing signing key through to the token endpoint 2020-12-03 17:16:08 -08:00
Matt Moyer
fa94ebfbd1 Merge pull request #229 from vmware-tanzu/callback-endpoint
Implement supervisor OIDC upstream callback endpoint used during authorize flow
2020-12-03 16:28:02 -06:00
Matt Moyer
c18c670765 Merge remote-tracking branch 'origin/main' into callback-endpoint 2020-12-03 14:53:26 -06:00
Matt Moyer
f410da0ed2 Merge pull request #242 from rajat404/refactor-docs
Remove duplicate docs from the repo and change all links to point to …
2020-12-03 14:52:51 -06:00
Andrew Keesler
58237d0e7d WIP: start to wire signing key into token handler
This commit includes a failing test (amongst other compiler failures) for the
dynamic signing key fetcher that we will inject into fosite. We are checking it
in so that we can pass the WIP off.

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-03 15:37:25 -05:00
Matt Moyer
c8abc79d9b Fix this comment (and retrigger CI).
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 14:24:26 -06:00
Matt Moyer
9455a66be8 This trailing dash is now taken care of by the library method.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 13:56:24 -06:00
aram price
05085d8e23 Use anonymous interface in test for Storage 2020-12-03 11:26:36 -08:00
Matt Moyer
8563c05baf Tweak these timeouts to be a bit faster (and retrigger CI).
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 13:22:27 -06:00
Ryan Richard
67bf54a9f9 Use an interface for storage in token_handler_test.go
Signed-off-by: Aram Price <pricear@vmware.com>
2020-12-03 11:05:47 -08:00
Matt Moyer
408fbe4f76 Parameterize the supervisor_redirect_uri in the test env Dex.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 12:45:56 -06:00
Matt Moyer
cb5e494815 Dump out proxy access logs in TestSupervisorLogin.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 11:28:48 -06:00
Matt Moyer
954591d2db Add some debugging logs to our proxy client code.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 10:25:26 -06:00
Andrew Keesler
2f1a67ef0d Merge remote-tracking branch 'upstream/callback-endpoint' into token-endpoint 2020-12-03 11:14:37 -05:00
Matt Moyer
d7b1ab8e43 Try to capture more logs from the TestSupervisorLogin test.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 09:39:33 -06:00
Matt Moyer
1d44a0cdfa Add a small integration test library to dump pod logs on test failures.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-03 09:39:33 -06:00
Matt Moyer
1fa41c4d0a Merge remote-tracking branch 'origin/main' into callback-endpoint 2020-12-03 08:50:31 -06:00
Matt Moyer
0deb7cc09a Merge pull request #250 from mattmoyer/fix-ipv6-test-regression
Fix a test regression with IPv6 localhost interfaces.
2020-12-03 08:48:57 -06:00
Andrew Keesler
fe2e2bdff1 Our ID token signing algorithm is ES256, not RS256
We are currently using EC keys to sign ID tokens, so we should reflect that in
our OIDC discovery metadata.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-03 07:46:07 -05:00
Ryan Richard
95093ab0af Use kube storage for the supervisor callback endpoint's fosite sessions 2020-12-02 17:40:01 -08:00
Margo Crawford
1dd7c82af6 Added id token verification 2020-12-02 16:55:48 -08:00
Matt Moyer
64ef53402d In TestSupervisorLogin, wrap the discovery request in an Eventually().
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 18:07:52 -06:00
Matt Moyer
37c5e121c4 Fix a test issue with IPv6 localhost interfaces.
This fixes a regression introduced by 24c4bc0dd4. It could occasionally cause the tests to fail when run on a machine with an IPv6 localhost interface. As a fix I added a wrapper for the new Go 1.15 `LookupIP()` method, and created a partially-functional backport for Go 1.14. This should be easy to delete in the future.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 17:49:21 -06:00
Matt Moyer
879525faac Clean up the browsertest package a bit.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 17:20:24 -06:00
Ryan Richard
6ed9107df0 Remove a couple of todos that will be resolved in Slack conversations 2020-12-02 14:20:18 -08:00
Ryan Richard
c320132289 Back-fill some more unit tests on authorizationcode_test.go 2020-12-02 14:20:18 -08:00
Matt Moyer
ae9bdc1d61 Fix a lint warning by simplifying this append operation.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 16:11:40 -06:00
Matt Moyer
c0f13ef4ac Merge remote-tracking branch 'origin/main' into callback-endpoint
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 16:09:08 -06:00
Matt Moyer
f40144e1a9 Update TestSupervisorLogin to test the callback flow using a browser.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:35 -06:00
Matt Moyer
0ccf14801e Expose the MaskTokens function so other test code can use it.
This is just a small helper to make test output more readable.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:34 -06:00
Matt Moyer
273ac62ec2 Extend the test client helpers in ./test/library/client.go.
This adds a few new "create test object" helpers and extends `CreateTestOIDCProvider()` to optionally wait for the created OIDCProvider to enter some expected status condition.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:34 -06:00
Matt Moyer
545c26e5fe Refactor browser-related test functions to a ./test/library/browsertest package.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:34 -06:00
Matt Moyer
22953cdb78 Add a CA.Pool() method to ./internal/certauthority.
This is convenient for at least one test and is simple enough to write and test.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:34 -06:00
Matt Moyer
fe0481c304 In integration test env, deploy a ClusterIP service and register that with Dex.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:33 -06:00
Matt Moyer
fde56164cd Add a redirectURI parameter to ExchangeAuthcodeAndValidateTokens() method.
We missed this in the original interface specification, but the `grant_type=authorization_code` requires it, per RFC6749 (https://tools.ietf.org/html/rfc6749#section-4.1.3).

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:33 -06:00
Matt Moyer
4fe691de92 Save an http.Client with each upstreamoidc.ProviderConfig object.
This allows the token exchange request to be performed with the correct TLS configuration.

We go to a bit of extra work to make sure the `http.Client` object is cached between reconcile operations so that connection pooling works as expected.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:33 -06:00
Matt Moyer
c23c54f500 Add an explicit Path=/; to our CSRF cookie, per the spec.
> [...] a cookie named "__Host-cookie1" MUST contain a "Path" attribute with a value of "/".

https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-12-02 15:55:33 -06:00
Margo Crawford
9419b7392d WIP: start to validate ID token returned from token endpoint
This won't compile, but we are passing this between two teammates.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-02 16:26:47 -05:00
Andrew Keesler
09e6c86c46 token_handler.go: complete some TODOs and strengthen double auth code test
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-02 15:33:57 -05:00
Rajat Goyal
7e78c9322c Remove duplicate documentation images from the repo and change all links to point to the Hugo site 2020-12-02 23:58:19 +05:30
Rajat Goyal
31810a97e1 Remove duplicate docs from the repo and change all links to point to the Hugo site 2020-12-02 23:58:19 +05:30
Andrew Keesler
8e4c85d816 WIP: get linting and unit tests passing after token endpoint first draft
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-02 11:16:02 -05:00
Andrew Keesler
970be58847 token_handler.go: first draft of token handler, with a bunch of TODOs
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-12-02 11:14:45 -05:00
Margo Crawford
d60c184424 Add pkce and openidconnect storage
- Also refactor authorizationcode_test

Signed-off-by: Ryan Richard <rrichard@vmware.com>
2020-12-01 17:18:32 -08:00
Ryan Richard
f38c150f6a Finished tests for pkce storage and added it to kubestorage
- Also fixed some lint errors with v1.33.0 of the linter

Signed-off-by: Margo Crawford <margaretc@vmware.com>
2020-12-01 14:53:22 -08:00
Margo Crawford
c8eaa3f383 WIP towards using k8s fosite storage in the supervisor's callback endpoint
- Note that this WIP commit includes a failing unit test, which will
  be addressed in the next commit

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-12-01 11:01:42 -08:00
Matt Moyer
be8f11fe5a Merge pull request #246 from mattmoyer/build-on-go-1.14
Tweak some stdlib usage so we compile under Go 1.14.
2020-11-30 17:38:19 -06:00
Matt Moyer
b272b3f331 Refactor oidcclient.Login to use new upstreamoidc package.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 17:37:14 -06:00
Matt Moyer
4b60c922ef Add generated mock of UpstreamOIDCIdentityProviderI.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 17:37:14 -06:00
Matt Moyer
25ee99f93a Add ValidateToken method to UpstreamOIDCIdentityProviderI interface.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 17:37:14 -06:00
Matt Moyer
d32583dd7f Move OIDC Token structs into a new oidctypes package.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 17:02:03 -06:00
Matt Moyer
d64acbb5a9 Add upstreamoidc.ProviderConfig type implementing provider.UpstreamOIDCIdentityProviderI.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 15:22:56 -06:00
Matt Moyer
24c4bc0dd4 Tweak some stdlib usage so we compile under Go 1.14.
Mainly, avoid using some `testing` helpers that were added in 1.14, as well as a couple of other niceties we can live without.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-30 10:11:41 -06:00
Andrew Keesler
58a3e35c51 Revert "test/integration: skip TestSupervisorLogin until new callback logic is on main"
This reverts commit eae6d355f8.

We have added the new callback path logic (see b21f003), so we can stop skipping
this test.
2020-11-30 11:07:25 -05:00
Andrew Keesler
25bbd28527 Merge remote-tracking branch 'upstream/main' into callback-endpoint 2020-11-30 11:06:20 -05:00
Andrew Keesler
385d2db445 Merge pull request #245 from ankeesler/fix-supervisor-login-test
Run TestSupervisorLogin only on valid HTTP/HTTPS supervisor addresses
2020-11-30 11:05:43 -05:00
Andrew Keesler
eae6d355f8 test/integration: skip TestSupervisorLogin until new callback logic is on main
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-30 10:12:03 -05:00
Andrew Keesler
5be46d0bb7 test/integration: get downstream issuer path from upstream redirect
See comment in the code.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-30 09:58:08 -05:00
Andrew Keesler
5b04192945 Run TestSupervisorLogin only on valid HTTP/HTTPS supervisor addresses
We were assuming that env.SupervisorHTTPAddress was set, but it might not be
depending on the environment on which the integration tests are being run. For
example, in our acceptance environments, we don't currently set
env.SupervisorHTTPAddress.

I tried to follow the pattern from TestSupervisorOIDCDiscovery here.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-30 09:23:12 -05:00
Ryan Richard
e6b6c0e3ab Merge branch 'main' into callback-endpoint 2020-11-20 15:50:26 -08:00
Matt Moyer
dfb6544171 Merge pull request #238 from jknostman3/patch-1
Update site demo to use pinniped-concierge namespace
2020-11-20 17:15:26 -06:00
Matt Moyer
3596610f40 Merge pull request #239 from enj/enj/f/fosite_defaults
Set defaults for fosite config
2020-11-20 17:14:05 -06:00
Ryan Richard
ccddeb4cda Merge branch 'main' into callback-endpoint 2020-11-20 15:13:25 -08:00
Monis Khan
d39cc08b66 Set defaults for fosite config
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-20 17:18:52 -05:00
Ryan Richard
c4ff1ca304 auth_handler.go: Ignore invalid CSRF cookies rather than return error
Generate a new cookie for the user and move on as if they had not sent
a bad cookie. Hopefully this will make the user experience better if,
for example, the server rotated cookie signing keys and then a user
submitted a very old cookie.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-20 13:56:35 -08:00
Andrew Keesler
b21f0035d7 callback_handler.go: Get upstream name from state instead of path
Also use ConstantTimeCompare() to compare CSRF tokens to prevent
leaking any information in how quickly we reject bad tokens.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-20 13:33:08 -08:00
Matt Moyer
ad9439eef2 Merge pull request #207 from vmware-tanzu/dependabot/docker/golang-1.15.5
Bump golang from 1.15.3 to 1.15.5
2020-11-20 15:18:23 -06:00
Ryan Richard
72321fc106 Use /callback (without IDP name) path for callback endpoint (part 1)
This is much nicer UX for an administrator installing a UpstreamOIDCProvider
CRD. They don't have to guess as hard at what the callback endpoint path should
be for their UpstreamOIDCProvider.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-20 16:14:45 -05:00
Andrew Keesler
541019eb98 callback_handler.go: simplify stored ID token claims
Fosite is gonna set these fields for us.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-20 15:36:51 -05:00
Jake Knostman
15bffc6b16 Update site demo to use pinniped-concierge namespace 2020-11-20 12:31:23 -08:00
dependabot[bot]
901242c1e1 Bump golang from 1.15.3 to 1.15.5
Bumps golang from 1.15.3 to 1.15.5.

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-20 20:19:51 +00:00
Matt Moyer
fd0e0bb4c9 Merge pull request #234 from rajat404/main
Avoid printing the error message twice from client
2020-11-20 13:29:35 -06:00
Rajat Goyal
53bece2186 Avoid printing the error message twice from client 2020-11-21 00:05:26 +05:30
Matt Moyer
1a881e4f2b Merge pull request #232 from mattmoyer/adjust-test-environment-upstream-clients
Split test environment variables so there's a specific supervisor upstream client.
2020-11-20 09:46:04 -06:00
Andrew Keesler
488d1b663a internal/oidc/provider/manager: route to callback endpoint
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-20 10:44:56 -05:00
Andrew Keesler
8f5d1709a1 callback_handler.go: assert behavior about PKCE and IDSession storage
Also aggresively refactor for readability:
- Make helper validations functions for each type of storage
- Try to label symbols based on their downstream/upstream use and group them
  accordingly

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-20 09:41:49 -05:00
Matt Moyer
bc700d58ae Split test environment variables so there's a specific supervisor upstream client.
Prior to this we re-used the CLI testing client to test the authorize flow of the supervisor, but they really need to be separate upstream clients. For example, the supervisor client should be a non-public client with a client secret and a different callback endpoint.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-20 08:03:06 -06:00
Andrew Keesler
f8d76066c5 callback_handler.go: assert nonce is stored correctly
I think we want to do this here since we are storing all of the
other ID token claims?

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-20 08:38:23 -05:00
Mo Khan
b8fb37b9f6 Merge pull request #233 from enj/enj/i/tmp_disable_max_flight
Temporarily disable max inflight checks for mutating requests
2020-11-19 22:51:03 -05:00
Monis Khan
4a28d1f800 Temporarily disable max inflight checks for mutating requests
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-19 21:21:10 -05:00
Andrew Keesler
b25696a1fb callback_handler.go: Prepend iss to sub when making default username
- Also handle several more error cases
- Move RequireTimeInDelta to shared testutils package so other tests
  can also use it
- Move all of the oidc test helpers into a new oidc/oidctestutils
  package to break a circular import dependency. The shared testutil
  package can't depend on any of our other packages or else we
  end up with circular dependencies.
- Lots more assertions about what was stored at the end of the
  request to build confidence that we are going to pass all of the
  right settings over to the token endpoint through the storage, and
  also to avoid accidental regressions in that area in the future

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-19 17:57:07 -08:00
Andrew Keesler
b49d37ca54 callback_handler.go: test invalid upstream ID token username/groups
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-19 15:53:21 -05:00
Mo Khan
20b62b8841 Merge pull request #231 from enj/enj/f/fosite_kube_storage
Add kube based storage for use with fosite
2020-11-19 15:34:55 -05:00
Ryan Richard
83101eefce callback_handler.go: start to test upstream token corner cases
Also refactor to get rid of duplicate test structs.

Also also don't default groups ID token claim because there is no standard one.

Also also also add some logging that will hopefully help us in debugging in the
future.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 14:19:01 -05:00
Monis Khan
86865d155a Switch fuzzing test to UTC
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-19 14:04:25 -05:00
Monis Khan
3575be7742 Add authorization code storage
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-19 13:18:27 -05:00
Monis Khan
b7d823a077 Add generic Kube API based CRUD storage
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-19 13:18:02 -05:00
Ryan Richard
a47617cad0 callback_handler.go: Add JWT Audience claim to storage 2020-11-19 08:53:53 -08:00
Ryan Richard
ee84f31f42 callback_handler.go: Add JWT Issuer claim to storage 2020-11-19 08:35:23 -08:00
Andrew Keesler
ace861f722 callback_handler.go: get some thoughts down about default upstream claims
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 11:08:21 -05:00
Andrew Keesler
2e62be3ebb callback_handler.go: assert correct args are passed to token exchange
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 10:20:46 -05:00
Andrew Keesler
48e0250649 callback_handler.go: test that we request openid scope correctly
Also add some testing.T.Log() calls to make debugging handler test failures
easier.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 09:28:56 -05:00
Andrew Keesler
6c72507bca callback_handler.go: add test for failed upstream exchange/validation
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 09:00:41 -05:00
Andrew Keesler
63b8c6e4b2 callback_handler.go: test when state missing a needed param
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 08:51:23 -05:00
Andrew Keesler
ffdb7fa795 callback_handler.go: add a test for invalid state auth params
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-19 08:41:44 -05:00
Ryan Richard
652ea6bd2a Start using fosite in the Supervisor's callback handler 2020-11-18 17:15:01 -08:00
Mo Khan
3bc5952f7e Merge pull request #227 from mattmoyer/add-authorizationconfig-omitempty
Use `omitempty` on UpstreamOIDCProvider `spec.authorizationConfig` field.
2020-11-18 20:10:55 -05:00
Matt Moyer
7520dadbdd Use omitempty on UpstreamOIDCProvider spec.authorizationConfig field.
This allows you to omit the field in creation requests, which was annoying.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-18 17:14:35 -06:00
Mo Khan
8a4be431f6 Merge pull request #230 from vmware-tanzu/scc
Add nonroot SCC to work on OpenShift clusters
2020-11-18 17:46:01 -05:00
Mo Khan
c32e452db8 Add nonroot SCC to work on OpenShift clusters 2020-11-18 17:08:45 -05:00
Ryan Richard
24bd8b2e42 Merge pull request #226 from absoludity/fix-getting-started4
Fix demo.md and update default namespace for pinniped concierge.
2020-11-18 13:39:04 -08:00
Ryan Richard
227fbd63aa Use an interface instead of a concrete type for UpstreamOIDCIdentityProvider
Because we want it to implement an AuthcodeExchanger interface and
do it in a way that will be more unit test-friendly than the underlying
library that we intend to use inside its implementation.
2020-11-18 13:38:13 -08:00
Ryan Richard
c83cec341b Merge branch 'main' into fix-getting-started4 2020-11-17 15:02:36 -08:00
Matt Moyer
7404ee4531 Merge pull request #224 from mattmoyer/make-oidcclient-public
Move `./internal/oidcclient` to `./pkg/oidcclient`.
2020-11-17 15:13:50 -06:00
Matt Moyer
e0a9bef6ce Move ./internal/oidcclient to ./pkg/oidcclient.
This will allow it to be imported by Go code outside of our repository, which was something we have planned for since this code was written.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-17 14:53:32 -06:00
Matt Moyer
428b9f2758 Merge pull request #223 from mattmoyer/refactor-cert-gen
Refactor certificate generation for integration test Dex.
2020-11-17 12:45:20 -06:00
Matt Moyer
0d1ad6e1df Fix some broken resource grouping/ordering in Tiltfile.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-17 12:21:15 -06:00
Matt Moyer
6ce2f109bf Refactor certificate generation for integration test Dex.
Before, we did this in an init container, which meant if the Dex pod restarted we would have fresh certs, but our Tilt/bash setup didn't account for this.

Now, the certs are generated by a Job which runs once and saves the generated files into a Secret. This should be a bit more stable.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-17 11:36:36 -06:00
Matt Moyer
3b9fb71dd1 Merge pull request #222 from mattmoyer/readd-supervisor-login-tests
Re-add the TestSupervisorLogin integration test.
2020-11-17 11:16:01 -06:00
Ryan Richard
97552aec5f Merge branch 'main' into callback-endpoint 2020-11-17 09:06:54 -08:00
Matt Moyer
d6d808d185 Re-add the TestSupervisorLogin integration test.
This is 99% Andrew's code from 4032ed32ae, but tweaked to work with the new UpstreamOIDCProvider setup.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-17 09:21:17 -06:00
Matt Moyer
b75a6cdb76 Merge pull request #221 from mattmoyer/use-https-dex
Add support for custom CA bundle in CLI and UpstreamOIDCProvider.
2020-11-16 20:47:16 -06:00
Matt Moyer
b31deff0fb Update integration tests to use HTTPS Dex for UpstreamOIDCProvider testing.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 20:23:20 -06:00
Matt Moyer
ee978fdde8 Add controller support for spec.tls field.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 20:23:20 -06:00
Matt Moyer
e867fb82b9 Add spec.tls field to UpstreamOIDCProvider API.
This allows for a custom CA bundle to be used when connecting to the upstream issuer.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 20:23:20 -06:00
Matt Moyer
b17ac6ec0b Update integration tests to run Dex over HTTPS.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 20:23:20 -06:00
Matt Moyer
dd2133458e Add --ca-bundle flag to "pinniped login oidc" command.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 18:15:20 -06:00
Matt Moyer
e7ecfd3954 Merge pull request #219 from mattmoyer/add-test-proxy
Convert CLI tests to work through an HTTP forward proxy.
2020-11-16 17:48:16 -06:00
Matt Moyer
c8b17978a9 Convert CLI tests to work through an HTTP forward proxy.
This change deploys a small Squid-based proxy into the `dex` namespace in our integration test environment. This lets us use the cluster-local DNS name (`http://dex.dex.svc.cluster.local/dex`) as the OIDC issuer. It will make generating certificates easier, and most importantly it will mean that our CLI can see Dex at the same name/URL as the supervisor running inside the cluster.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-16 17:16:58 -06:00
Matt Moyer
a4733025ce Merge pull request #220 from jonasrosland/fix-landing-text
Fix landing page use cases
2020-11-16 16:36:44 -06:00
Andrew Keesler
1c7601a2b5 callback_handler.go: start happy path test with redirect
Next steps: fosite storage?

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-16 17:07:34 -05:00
Ryan Richard
052cdc40dc callback_handler.go: add CSRF and version state validations
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-16 14:41:00 -05:00
jonasrosland
332ed8e50b Fix landing page use cases
Signed-off-by: jonasrosland <jrosland@vmware.com>
2020-11-16 12:00:06 -05:00
Andrew Keesler
4138c9244f callback_handler.go: write 2 invalid cookie tests
Also common-ize some more constants shared between the auth and callback
endpoints.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-16 11:47:49 -05:00
Michael Nelson
57a2dc9fc1 Update default namespace for pinniped-concierge to match install-pinniped-concierge.yaml 2020-11-16 11:05:53 +11:00
Michael Nelson
9bb9402e89 Updated doc/demo.md with required namespace 2020-11-16 11:05:53 +11:00
Andrew Keesler
3ef1171667 Tiny bit more code for Supervisor's callback_handler.go
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-13 15:59:51 -08:00
Matt Moyer
84b61fac88 Merge pull request #215 from mattmoyer/fix-upstream-oidc-provider
Fix some issues in the UpstreamOIDCProvider CRD and controller
2020-11-13 17:23:10 -06:00
Matt Moyer
c10393b495 Mask the raw error messages from go-oidc, since they are dangerous.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 16:22:34 -06:00
Matt Moyer
d3d8ef44a0 Make more fields in UpstreamOIDCProvider optional.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 15:28:37 -06:00
Mo Khan
d5ee925e62 Merge pull request #213 from mattmoyer/more-categories
Add our TokenCredentialRequest to the "pinniped" API category as well.
2020-11-13 15:51:42 -05:00
Mo Khan
47d216caae Merge pull request #209 from alexbrand/doc-fixes
Fix broken links in the project's website
2020-11-13 15:51:13 -05:00
Alexander Brand
406d6b5544 docs/scope.md: Fix link to contrib guide
Signed-off-by: Alexander Brand <alexbrand09@gmail.com>
2020-11-13 15:25:01 -05:00
Matt Moyer
ab87977c08 Put our TokenCredentialRequest API into the "pinniped" category.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 14:22:26 -06:00
Matt Moyer
f4dfc22f8e Merge pull request #212 from enj/enj/i/restore_cert_ttl
Reduce client cert TTL back to 5 mins
2020-11-13 14:11:44 -06:00
Matt Moyer
785a1d14fb Merge pull request #199 from mattmoyer/add-oidc-upstream-crd
Add UpstreamOIDCProvider API and initial controller.
2020-11-13 13:01:13 -06:00
Matt Moyer
d68a4b85f4 Add integration tests for UpstreamOIDCProvider status.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 12:30:38 -06:00
Matt Moyer
cbd71df574 Add "upstream-watcher" controller to supervisor.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 12:30:38 -06:00
Monis Khan
c05cbca0b0 Reduce client cert TTL back to 5 mins
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-13 13:30:02 -05:00
Matt Moyer
2e7d869ccc Add generated API/client code for new UpstreamOIDCProvider CRD.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 11:38:50 -06:00
Matt Moyer
bac3c19bec Add UpstreamOIDCProvider API type definition.
This is essentially just a copy of Andrew's work from https://github.com/vmware-tanzu/pinniped/pull/135.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-13 11:38:49 -06:00
Andrew Keesler
81b9a48437 callback_handler.go: initial API/test shape with 1 test
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-13 12:32:35 -05:00
Alexander Brand
271640b66d docs/architecture.md: Fix broken link 2020-11-13 09:17:47 -05:00
Alexander Brand
6b0d4184d5 docs/architecture.md: Fix broken link 2020-11-13 09:15:46 -05:00
Ryan Richard
d351ef430c Merge pull request #206 from vmware-tanzu/authorize_endpoint_reuse_cookie
Supervisor authorize endpoint reuses existing CSRF cookies and signs new ones
2020-11-12 16:26:01 -08:00
Matt Moyer
e6f128e2a7 Merge pull request #205 from mattmoyer/more-careful-categories
Put all of our APIs into a "pinniped" category, and never use "all".
2020-11-12 17:37:20 -06:00
Andrew Keesler
080bb594b2 Supervisor authorize endpoint reuses existing CSRF cookies and signs new ones
- To better support having multiple downstream providers configured,
  the authorize endpoint will share a CSRF cookie between all
  downstream providers' authorize endpoints. The first time a
  user's browser hits the authorize endpoint of any downstream
  provider, that endpoint will set the cookie. Then if the user
  starts an authorize flow with that same downstream provider or with
  any other downstream provider which shares the same domain name
  (i.e. differentiated by issuer path), then the same cookie will be
  submitted and respected.
- Just in case we are sharing the domain name with some other app,
  we sign the value of any new CSRF cookie and check the signature
  when we receive the cookie. This wasn't strictly necessary since
  we probably won't share a domain name with other apps, but it
  wasn't hard to add this cookie signing.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-12 15:36:59 -08:00
Matt Moyer
f1696411d9 Test that Pinniped APis do not have short names, either.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-12 17:13:52 -06:00
Matt Moyer
5580ca82ac Merge pull request #204 from mattmoyer/cleanup-update-script
Remove CRD count check, since we can now use wildcards.
2020-11-12 16:28:24 -06:00
Matt Moyer
7f2c43cd62 Put all of our APIs into a "pinniped" category, and never use "all".
We want to have our APIs respond to `kubectl get pinniped`, and we shouldn't use `all` because we don't think most average users should have permission to see our API types, which means if we put our types there, they would get an error from `kubectl get all`.

I also added some tests to assert these properties on all `*.pinniped.dev` API resources.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-12 16:26:34 -06:00
Matt Moyer
372cfe1601 Remove CRD count check, since we can now use wildcards.
This check predates the API renaming we did. Now that our API groups have `concierge`/`supervisor` in the name, we don't need to maintain a specific set of `cp` commands and keep them in sync, so we don't really need this check.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-12 15:48:03 -06:00
Mo Khan
d73fdb1d33 Merge pull request #202 from mattmoyer/remove-internal-crd-packages
Remove extraneous internal packages for CRD APIs.
2020-11-12 15:29:29 -05:00
Matt Moyer
821190004c Remove extraneous internal packages for CRD APIs.
These only really make sense for aggregated API types where we need `conversion-gen` to do version conversion.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-12 14:04:53 -06:00
Andrew Keesler
8321773a22 auth_handler.go: fix lint error
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-12 12:24:40 -05:00
Andrew Keesler
3a943a3b9a auth_handler.go: ignore encoding timestamp for deterministic tests
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-12 12:14:50 -05:00
Ryan Richard
6d380c629a auth_handler.go: use encryption in tests
Our unit tests are gonna touch a lot more corner cases than our
integration tests, so let's make them run as close to the real
implementation as possible.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-12 12:14:49 -05:00
Matt Moyer
5fd105496f Merge pull request #201 from amymanion/am-dev
Style updates
2020-11-12 09:12:24 -06:00
Matt Moyer
b3e622c914 Merge pull request #200 from jonasrosland/website-fixes
Website fixes for broken links, formatting, and more
2020-11-12 09:10:28 -06:00
Amy Manion
c4ed768c9e Adjust hero font size 2020-11-12 09:46:44 -05:00
Amy Manion
ef11f97a75 Style updates
-adjust font sizes
-fix ordered lists

Signed-off-by: Amy Manion <amy.manion@principlestudios.com>
2020-11-12 09:35:17 -05:00
Jonas Rosland
0b41469527 Website fixes for broken links, formatting, and more
Signed-off-by: Jonas Rosland <jrosland@vmware.com>
2020-11-11 21:40:49 -05:00
Mo Khan
8859172025 Merge pull request #198 from enj/enj/i/multi_api_service
Prevent multiple pinnipeds from thrashing on the API service
2020-11-11 20:44:42 -05:00
Monis Khan
9c8b081906 Prevent multiple pinnipeds from thrashing on the API service
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-11 20:09:49 -05:00
Ryan Richard
300d522eb0 Merge pull request #185 from vmware-tanzu/authorize_endpoint 2020-11-11 16:03:15 -08:00
Ryan Richard
203e040be1 Remove an unfinished integration test
This commit is meant to be reverted when we are unblocked and
ready to start working on this integration test again. Temporarily
remove it so we can merge this PR to main.

Note: I had tried using t.Skip() in the test, but then that caused lint
failures, so decided to just remove it for now.
2020-11-11 15:40:40 -08:00
Matt Moyer
fdcea0de05 Merge pull request #197 from jonasrosland/a-seal-of-approval
Add first blog post
2020-11-11 17:33:40 -06:00
Monis Khan
db6fc234b7 Add NullStorage for the authorize endpoint to use
We want to run all of the fosite validations in the authorize
endpoint, but we don't need to store anything yet because
we are storing what we need for later in the upstream state
parameter.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-11 14:49:24 -08:00
jonasrosland
e6838ace6b Add first blog post
Signed-off-by: jonasrosland <jrosland@vmware.com>
2020-11-11 17:06:36 -05:00
Ryan Richard
4b8c1de647 Add unit test to auth_handler_test.go for non-openid authorize requests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-11 13:13:57 -08:00
Andrew Keesler
c2262773e6 Finish the WIP from the previous commit for saving authorize endpoint state
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-11 12:29:14 -08:00
Andrew Keesler
f806768039 Merge pull request #196 from ankeesler/ytt-logging
Add YTT template value for log level
2020-11-11 09:29:24 -05:00
Andrew Keesler
83a156d72b Enable debug logging in all testing scenarios
It is really helpful to have verbose logs during test debugging.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-11 09:01:43 -05:00
Andrew Keesler
724c0d3eb0 Add YTT template value for setting log level
This is helpful for us, amongst other users, because we want to enable "debug"
logging whenever we deploy components for testing.

See a5643e3 for addition of log level.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-11 09:01:38 -05:00
Monis Khan
dd190dede6 WIP for saving authorize endpoint state into upstream state param
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-10 17:58:00 -08:00
Matt Moyer
5b8e0c4d99 Merge pull request #195 from mattmoyer/fix-links
Fix some links on the community page.
2020-11-10 17:22:37 -06:00
Matt Moyer
b2b8d5457d Fix some links on the community page.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-10 17:19:30 -06:00
Matt Moyer
16ef0b2d41 Merge pull request #194 from jonasrosland/website-fixes
Minor website fixes and adding netlify configs
2020-11-10 16:24:51 -06:00
jonasrosland
d097de7fdf Minor website fixes and adding netlify configs
Signed-off-by: jonasrosland <jrosland@vmware.com>
2020-11-10 16:03:07 -05:00
Matt Moyer
101394c714 Merge pull request #188 from smalltalk-ai/main
Hugo version of Pinniped site
2020-11-10 14:51:45 -06:00
Matt Moyer
06df825dab Merge pull request #193 from mattmoyer/add-extra-sites
Add Netlify configs for extra redirect domains.
2020-11-10 14:03:37 -06:00
Matt Moyer
f7efc360a0 Add Netlify configs for extra redirect domains.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-10 13:58:31 -06:00
Amy Manion
ad74f259de Content updates
-remove extra blog posts
-remove extra images
-replace Andrew’s picture
2020-11-10 13:39:13 -05:00
Andrew Keesler
005225d5f9 Use the new plog pkg in auth_handler.go
- Add a new helper method to plog to make a consistent way to log
  expected errors at the info level (as opposed to unexpected
  system errors that would be logged using plog.Error)

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-10 10:33:52 -08:00
Ryan Richard
b9726615dd Merge branch 'main' into authorize_endpoint 2020-11-10 09:29:21 -08:00
Ryan Richard
01941d6b2a Run Tilt containers as root because live-reload breaks otherwise 2020-11-10 09:27:44 -08:00
Ryan Richard
b21c27b219 Merge branch 'main' into authorize_endpoint 2020-11-10 09:24:19 -08:00
Mo Khan
9bfcaa33c6 Merge pull request #190 from enj/enj/f/klog_levels
Add log level support
2020-11-10 12:14:02 -05:00
Monis Khan
1c60e09f13 Make race detector happy by removing parallelism
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-10 11:23:42 -05:00
Monis Khan
15a5332428 Reduce log spam
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-10 10:22:27 -05:00
Monis Khan
a5643e3738 Add log level support
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-10 10:22:27 -05:00
Monis Khan
9356f64c55 Remove global klog --log-flush-frequency flag
Signed-off-by: Monis Khan <mok@vmware.com>
2020-11-10 08:48:42 -05:00
Ryan Richard
246471bc91 Also run OIDC validations in supervisor authorize endpoint
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-06 14:44:58 -08:00
Adam Powell
896e1b45f0 Hugo version of Pinniped site 2020-11-06 12:42:57 -10:00
Andrew Keesler
4032ed32ae Auth endpoint integration test initial thoughts
This is awaiting the new upstream OIDC provider CRD in order
to pass, however hopefully this is a starting point for us.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-05 11:00:05 -05:00
Ryan Richard
33ce79f89d Expose the Supervisor OIDC authorization endpoint to the public 2020-11-04 17:06:47 -08:00
Andrew Keesler
3bc13517b2 prepare-for-integration-tests.sh: add check for chromedriver
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 15:53:32 -08:00
Andrew Keesler
a36f7c6c07 Test that the port of localhost redirect URI is ignored during validation
Also move definition of our oauth client and the general fosite
configuration to a helper so we can use the same config to construct
the handler for both test and production code.

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-04 15:04:50 -08:00
Ryan Richard
ba688f56aa Supervisor authorize endpoint errors when PKCE code_challenge_method is invalid
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 12:29:43 -08:00
Matt Moyer
8684f8f628 Merge pull request #139 from enj/enj/i/use_parent_func
Use parent func to indicate when the controller queue is a singleton
2020-11-04 14:21:50 -06:00
Andrew Keesler
2564d1be42 Supervisor authorize endpoint errors when missing PKCE params
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-04 12:19:07 -08:00
Matt Moyer
4da3d93f6e The supervisor JWKS observer and TLS cert controllers use the ctx after all, whoops.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-04 13:08:50 -06:00
Ryan Richard
0045ce4286 Refactor auth_handler_test.go's creation of paths and urls to use helpers 2020-11-04 09:58:40 -08:00
Monis Khan
418f4d20ae Use parent func to indicate when the controller queue is a singleton
This prevents unnecessary sync loop runs when the controller is
running with a single worker.  When the controller is running with
more than one worker, it prevents subtle bugs that can cause the
controller to go "back in time."

Signed-off-by: Monis Khan <mok@vmware.com>
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-04 11:08:10 -06:00
Ryan Richard
8a7e22e63e @ankeesler: Maybe, but not this time ;) 2020-11-04 08:43:45 -08:00
Andrew Keesler
9e4ffd1cce One of these days I will get here.Doc() spacing correct
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 11:29:33 -05:00
Andrew Keesler
6fe455c687 auth_handler.go: comment out currently unused fosite wiring
See e8f4336 for why this is here in the first place.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 11:20:03 -05:00
Andrew Keesler
d8c8f04860 auth_handler.go: write some more negative tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 11:12:26 -05:00
Andrew Keesler
e8f433643f auth_handler.go: only inject oauth store into handler
Previously we were injecting the whole oauth handler chain into this function,
which meant we were essentially writing unit tests to test our tests. Let's push
some of this logic into the source code.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 10:35:26 -05:00
Andrew Keesler
4f95e6a372 auth_handler.go: add test for invalid downstream redirect uri
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 10:30:53 -05:00
Andrew Keesler
259ffb5267 Checkpoint: write a single negative test using fosite
Bringing in fosite to our go.mod introduced those other go.mod changes.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-04 10:15:19 -05:00
Andrew Keesler
aab0fd644f Merge remote-tracking branch 'upstream/main' into authorize_endpoint 2020-11-04 10:14:54 -05:00
Andrew Keesler
e7a817e67a Merge pull request #186 from ankeesler/bump-jose
gopkg.in/square/go-jose.v2: v2.2.2 -> v2.5.1
2020-11-04 10:14:32 -05:00
Andrew Keesler
0bbf55e46f gopkg.in/square/go-jose.v2: v2.2.2 -> v2.5.1
We were behind for some reason. Probably makes sense to bump to
latest version to get bug fixes and such.
2020-11-04 09:55:18 -05:00
Ryan Richard
c34e5a727d Starting the implementation of an OIDC authorization endpoint handler
Does not validate incoming request parameters yet. Also is not
served on the http/https ports yet. Those will come in future commits.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-03 16:17:38 -08:00
Andrew Keesler
0d8477ea8a Add a type for in-memory caching of upstream OIDC Identity Providers
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-11-03 12:06:07 -08:00
883 changed files with 55466 additions and 16488 deletions

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
./.*
./*.md
./*.yaml
./apis
./deploy
./Dockerfile
./generated/1.1*
./hack/lib/tilt/
./internal/mocks
./LICENSE
./site/
./test
**/*_test.go

13
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
codecov:
strict_yaml_branch: main
require_ci_to_pass: no
notify:
wait_for_ci: no
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true

View File

@@ -30,7 +30,6 @@ linters:
- gocritic
- gocyclo
- godot
- goerr113
- goheader
- goimports
- golint
@@ -56,15 +55,18 @@ issues:
linters:
- funlen
- gochecknoglobals
- goerr113
linters-settings:
funlen:
lines: 125
lines: 150
statements: 50
goheader:
values:
regexp:
# YYYY or YYYY-YYYY
YEARS: \d\d\d\d(-\d\d\d\d)?
template: |-
Copyright 2020 the Pinniped contributors. All Rights Reserved.
Copyright {{YEARS}} the Pinniped contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
goimports:
local-prefixes: go.pinniped.dev

View File

@@ -15,3 +15,9 @@ repos:
- id: detect-private-key
exclude: testdata
- id: mixed-line-ending
- repo: local
hooks:
- id: validate-copyright-year
name: Validate copyright year
entry: hack/check-copyright-year.sh
language: script

View File

@@ -8,24 +8,17 @@ Please see the [Code of Conduct](./CODE_OF_CONDUCT.md).
## Project Scope
Learn about the [scope](doc/scope.md) of the project.
Learn about the [scope](https://pinniped.dev/docs/scope/) of the project.
## Meeting with the Maintainers
## Community Meetings
The maintainers aspire to hold a video conference every other week with the Pinniped community.
Any community member may request to add topics to the agenda by contacting a [maintainer](MAINTAINERS.md)
in advance, or by attending and raising the topic during time remaining after the agenda is covered.
Typical agenda items include topics regarding the roadmap, feature requests, bug reports, pull requests, etc.
A [public document](https://docs.google.com/document/d/1qYA35wZV-6bxcH5375vOnIGkNBo7e4OROgsV4Sj8WjQ)
tracks the agendas and notes for these meetings.
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
These meetings are currently scheduled for the first and third Thursday mornings of each month
at 9 AM Pacific Time, using this [Zoom meeting](https://VMware.zoom.us/j/94638309756?pwd=V3NvRXJIdDg5QVc0TUdFM2dYRzgrUT09).
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
## Discussion
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page.
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
## Issues
@@ -97,16 +90,19 @@ docker build .
1. Install dependencies:
- [`chromedriver`](https://chromedriver.chromium.org/) (and [Chrome](https://www.google.com/chrome/))
- [`docker`](https://www.docker.com/)
- `htpasswd` (installed by default on MacOS, usually found in `apache2-utils` package for linux)
- [`kapp`](https://carvel.dev/#getting-started)
- [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start)
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
- [`tilt`](https://docs.tilt.dev/install.html)
- [`ytt`](https://carvel.dev/#getting-started)
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
- [`chromedriver`](https://chromedriver.chromium.org/) (and [Chrome](https://www.google.com/chrome/))
On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already):
```bash
brew install kind tilt-dev/tap/tilt k14s/tap/ytt kubectl chromedriver
brew install kind tilt-dev/tap/tilt k14s/tap/ytt k14s/tap/kapp kubectl chromedriver && brew cask install docker
```
1. Create a local Kubernetes cluster using `kind`:

View File

@@ -1,35 +1,41 @@
# Copyright 2020 the Pinniped contributors. All Rights Reserved.
# syntax = docker/dockerfile:1.0-experimental
# Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.15.3 as build-env
FROM golang:1.15.8 as build-env
WORKDIR /work
# Get dependencies first so they can be cached as a layer
COPY go.* ./
COPY generated/1.19/apis/go.* ./generated/1.19/apis/
COPY generated/1.19/client/go.* ./generated/1.19/client/
RUN go mod download
# Copy only the production source code to avoid cache misses when editing other files
COPY generated ./generated
COPY cmd ./cmd
COPY internal ./internal
COPY tools ./tools
COPY hack ./hack
COPY . .
ARG GOPROXY
# Build the executable binary (CGO_ENABLED=0 means static linking)
RUN mkdir out \
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./cmd/pinniped-concierge/... \
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./cmd/pinniped-supervisor/... \
&& CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o out ./cmd/local-user-authenticator/...
# Pass in GOCACHE (build cache) and GOMODCACHE (module cache) so they
# can be re-used between image builds.
RUN \
--mount=type=cache,target=/cache/gocache \
--mount=type=cache,target=/cache/gomodcache \
mkdir out && \
GOCACHE=/cache/gocache \
GOMODCACHE=/cache/gomodcache \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64 \
go build -v -ldflags "$(hack/get-ldflags.sh)" -o out \
./cmd/pinniped-concierge/... \
./cmd/pinniped-supervisor/... \
./cmd/local-user-authenticator/...
# Use a runtime image based on Debian slim
FROM debian:10.6-slim
# Use a Debian slim image to grab a reasonable default CA bundle.
FROM debian:10.8-slim AS get-ca-bundle-env
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* /var/cache/debconf/*
# Copy the binaries from the build-env stage
COPY --from=build-env /work/out/pinniped-concierge /usr/local/bin/pinniped-concierge
COPY --from=build-env /work/out/pinniped-supervisor /usr/local/bin/pinniped-supervisor
COPY --from=build-env /work/out/local-user-authenticator /usr/local/bin/local-user-authenticator
# Use a runtime image based on Debian slim.
FROM debian:10.8-slim
COPY --from=get-ca-bundle-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
# Copy the binaries from the build-env stage.
COPY --from=build-env /work/out/ /usr/local/bin/
# Document the ports
EXPOSE 8080 8443

View File

@@ -5,7 +5,9 @@ This is the current list of maintainers for the Pinniped project.
| Maintainer | GitHub ID | Affiliation |
| --------------- | --------- | ----------- |
| Andrew Keesler | [ankeesler](https://github.com/ankeesler) | [VMware](https://www.github.com/vmware/) |
| Margo Crawford | [margocrawf](https://github.com/margocrawf) | [VMware](https://www.github.com/vmware/) |
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) | [VMware](https://www.github.com/vmware/) |
| Mo Khan | [enj](https://github.com/enj) | [VMware](https://www.github.com/vmware/) |
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) | [VMware](https://www.github.com/vmware/) |
| Ryan Richard | [cfryanr](https://github.com/cfryanr) | [VMware](https://www.github.com/vmware/) |

View File

@@ -1,4 +1,4 @@
<img src="doc/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
## Overview
@@ -23,22 +23,39 @@ with IDPs, and distribution-specific integration strategies.
### Architecture
Pinniped offers credential exchange to enable a user to exchange an external IDP
credential for a short-lived, cluster-specific credential. Pinniped supports various
IDP types and implements different integration strategies for various Kubernetes
The Pinniped Supervisor component offers identity federation to enable a user to
access multiple clusters with a single daily login to their external IDP. The
Pinniped Supervisor supports various external [IDP
types](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#k8s-api-idp-supervisor-pinniped-dev-v1alpha1).
The Pinniped Concierge component offers credential exchange to enable a user to
exchange an external credential for a short-lived, cluster-specific
credential. Pinniped supports various [authentication
methods](https://github.com/vmware-tanzu/pinniped/tree/main/generated/1.20#authenticationconciergepinnipeddevv1alpha1)
and implements different integration strategies for various Kubernetes
distributions to make authentication possible.
To learn more, see [doc/architecture.md](doc/architecture.md).
The Pinniped Concierge can be configured to hook into the Pinniped Supervisor's
federated credentials, or it can authenticate users directly via external IDP
credentials.
<img src="doc/img/pinniped_architecture.svg" alt="Pinniped Architecture Sketch" width="300px"/>
To learn more, see [architecture](https://pinniped.dev/docs/architecture/).
<img src="site/content/docs/img/pinniped_architecture_concierge_supervisor.svg" alt="Pinniped Architecture Sketch"/>
## Trying Pinniped
Care to kick the tires? It's easy to [install and try Pinniped](doc/demo.md).
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/demo/).
## Community Meetings
Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
## Discussion
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page.
Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page or reach out in Kubernetes Slack Workspace within the [#pinniped channel](https://kubernetes.slack.com/archives/C01BW364RJA).
## Contributions

View File

@@ -1,12 +1,92 @@
# Reporting a Vulnerability
# Security Release Process
Pinniped development is sponsored by VMware, and the Pinniped team encourages users
who become aware of a security vulnerability in Pinniped to report any potential
vulnerabilities found to security@vmware.com. If possible, please include a description
of the effects of the vulnerability, reproduction steps, and a description of in which
version of Pinniped or its dependencies the vulnerability was discovered.
The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
Pinniped provides identity services for Kubernetes clusters. The community has adopted this security disclosure and response policy to ensure we responsibly handle critical issues.
The Pinniped team hopes that users encountering a new vulnerability will contact
us privately as it is in the best interests of our users that the Pinniped team has
an opportunity to investigate and confirm a suspected vulnerability before it becomes public knowledge.
## Supported Versions
As of right now, only the latest version of Pinniped is supported.
## Reporting a Vulnerability - Private Disclosure Process
Security is of the highest importance and all security vulnerabilities or suspected security vulnerabilities should be reported to Pinniped privately, to minimize attacks against current users of Pinniped before they are fixed. Vulnerabilities will be investigated and patched on the next patch (or minor) release as soon as possible. This information could be kept entirely internal to the project.
If you know of a publicly disclosed security vulnerability for Pinniped, please **IMMEDIATELY** contact the VMware Security Team (security@vmware.com). The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
**IMPORTANT: Do not file public issues on GitHub for security vulnerabilities**
To report a vulnerability or a security-related issue, please contact the VMware email address with the details of the vulnerability. The email will be fielded by the VMware Security Team and then shared with the Pinniped maintainers who have committer and release permissions. Emails will be addressed within 3 business days, including a detailed plan to investigate the issue and any potential workarounds to perform in the meantime. Do not report non-security-impacting bugs through this channel. Use [GitHub issues](https://github.com/vmware-tanzu/pinniped/issues/new/choose) instead.
## Proposed Email Content
Provide a descriptive subject line and in the body of the email include the following information:
* Basic identity information, such as your name and your affiliation or company.
* Detailed steps to reproduce the vulnerability (POC scripts, screenshots, and logs are all helpful to us).
* Description of the effects of the vulnerability on Pinniped and the related hardware and software configurations, so that the VMware Security Team can reproduce it.
* How the vulnerability affects Pinniped usage and an estimation of the attack surface, if there is one.
* List other projects or dependencies that were used in conjunction with Pinniped to produce the vulnerability.
## When to report a vulnerability
* When you think Pinniped has a potential security vulnerability.
* When you suspect a potential vulnerability but you are unsure that it impacts Pinniped.
* When you know of or suspect a potential vulnerability on another project that is used by Pinniped.
## Patch, Release, and Disclosure
The VMware Security Team will respond to vulnerability reports as follows:
1. The Security Team will investigate the vulnerability and determine its effects and criticality.
2. If the issue is not deemed to be a vulnerability, the Security Team will follow up with a detailed reason for rejection.
3. The Security Team will initiate a conversation with the reporter within 3 business days.
4. If a vulnerability is acknowledged and the timeline for a fix is determined, the Security Team will work on a plan to communicate with the appropriate community, including identifying mitigating steps that affected users can take to protect themselves until the fix is rolled out.
5. The Security Team will also create a [CVSS](https://www.first.org/cvss/specification-document) using the [CVSS Calculator](https://www.first.org/cvss/calculator/3.0). The Security Team makes the final call on the calculated CVSS; it is better to move quickly than making the CVSS perfect. Issues may also be reported to [Mitre](https://cve.mitre.org/) using this [scoring calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The CVE will initially be set to private.
6. The Security Team will work on fixing the vulnerability and perform internal testing before preparing to roll out the fix.
7. The Security Team will provide early disclosure of the vulnerability by emailing the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list. Distributors can initially plan for the vulnerability patch ahead of the fix, and later can test the fix and provide feedback to the Pinniped team. See the section **Early Disclosure to Pinniped Distributors List** for details about how to join this mailing list.
8. A public disclosure date is negotiated by the VMware SecurityTeam, the bug submitter, and the distributors list. We prefer to fully disclose the bug as soon as possible once a user mitigation or patch is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for distributor coordination. The timeframe for disclosure is from immediate (especially if its already publicly known) to a few weeks. For a critical vulnerability with a straightforward mitigation, we expect the report date for the public disclosure date to be on the order of 14 business days. The VMware Security Team holds the final say when setting a public disclosure date.
9. Once the fix is confirmed, the Security Team will patch the vulnerability in the next patch or minor release, and backport a patch release into all earlier supported releases. Upon release of the patched version of Pinniped, we will follow the **Public Disclosure Process**.
## Public Disclosure Process
The Security Team publishes a [public advisory](https://github.com/vmware-tanzu/pinniped/security/advisories) to the Pinniped community via GitHub. In most cases, additional communication via Slack, Twitter, mailing lists, blog and other channels will assist in educating Pinniped users and rolling out the patched release to affected users.
The Security Team will also publish any mitigating steps users can take until the fix can be applied to their Pinniped instances. Pinniped distributors will handle creating and publishing their own security advisories.
## Mailing lists
* Use security@vmware.com to report security concerns to the VMware Security Team, who uses the list to privately discuss security issues and fixes prior to disclosure. The use of encrypted email is encouraged. The public PGP key can be found at https://kb.vmware.com/kb/1055.
* Join the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list for early private information and vulnerability disclosure. Early disclosure may include mitigating steps and additional information on security patch releases. See below for information on how Pinniped distributors or vendors can apply to join this list.
## Early Disclosure to Pinniped Distributors List
The private list is intended to be used primarily to provide actionable information to multiple distributor projects at once. This list is not intended to inform individuals about security issues.
## Membership Criteria
To be eligible to join the [Pinniped Distributors](https://groups.google.com/g/project-pinniped-distributors) mailing list, you should:
1. Be an active distributor of Pinniped.
2. Have a user base that is not limited to your own organization.
3. Have a publicly verifiable track record up to the present day of fixing security issues.
4. Not be a downstream or rebuild of another distributor.
5. Be a participant and active contributor in the Pinniped community.
6. Accept the Embargo Policy that is outlined below.
7. Have someone who is already on the list vouch for the person requesting membership on behalf of your distribution.
**The terms and conditions of the Embargo Policy apply to all members of this mailing list. A request for membership represents your acceptance to the terms and conditions of the Embargo Policy.**
## Embargo Policy
The information that members receive on the Pinniped Distributors mailing list must not be made public, shared, or even hinted at anywhere beyond those who need to know within your specific team, unless you receive explicit approval to do so from the VMware Security Team. This remains true until the public disclosure date/time agreed upon by the list. Members of the list and others cannot use the information for any reason other than to get the issue fixed for your respective distribution's users.
Before you share any information from the list with members of your team who are required to fix the issue, these team members must agree to the same terms, and only be provided with information on a need-to-know basis.
In the unfortunate event that you share information beyond what is permitted by this policy, you must urgently inform the VMware Security Team (security@vmware.com) of exactly what information was leaked and to whom. If you continue to leak information and break the policy outlined here, you will be permanently removed from the list.
## Requesting to Join
Send new membership requests to https://groups.google.com/g/project-pinniped-distributors. In the body of your request please specify how you qualify for membership and fulfill each criterion listed in the Membership Criteria section above.
## Confidentiality, integrity and availability
We consider vulnerabilities leading to the compromise of data confidentiality, elevation of privilege, or integrity to be our highest priority concerns. Availability, in particular in areas relating to DoS and resource exhaustion, is also a serious security concern. The VMware Security Team takes all vulnerabilities, potential vulnerabilities, and suspected vulnerabilities seriously and will investigate them in an urgent and expeditious manner.

View File

@@ -1,8 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=authentication.concierge.pinniped.dev
// Package authentication is the internal version of the Pinniped concierge authentication API.
package authentication

View File

@@ -1,12 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

View File

@@ -3,7 +3,6 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/concierge/authentication
// +k8s:defaulter-gen=TypeMeta
// +groupName=authentication.concierge.pinniped.dev

View File

@@ -24,7 +24,7 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.
@@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&WebhookAuthenticator{},
&WebhookAuthenticatorList{},
&JWTAuthenticator{},
&JWTAuthenticatorList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -0,0 +1,83 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// Status of a JWT authenticator.
type JWTAuthenticatorStatus struct {
// Represents the observations of the authenticator's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
// Spec for configuring a JWT authenticator.
type JWTAuthenticatorSpec struct {
// Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is
// also used to validate the "iss" JWT claim.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^https://`
Issuer string `json:"issuer"`
// Audience is the required value of the "aud" JWT claim.
// +kubebuilder:validation:MinLength=1
Audience string `json:"audience"`
// Claims allows customization of the claims that will be mapped to user identity
// for Kubernetes access.
// +optional
Claims JWTTokenClaims `json:"claims"`
// TLS configuration for communicating with the OIDC provider.
// +optional
TLS *TLSSpec `json:"tls,omitempty"`
}
// JWTTokenClaims allows customization of the claims that will be mapped to user identity
// for Kubernetes access.
type JWTTokenClaims struct {
// Groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups".
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// +optional
Username string `json:"username"`
}
// JWTAuthenticator describes the configuration of a JWT authenticator.
//
// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid
// signature, existence of claims, etc.) and extract the username and groups from the token.
//
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:subresource:status
type JWTAuthenticator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the authenticator.
Spec JWTAuthenticatorSpec `json:"spec"`
// Status of the authenticator.
Status JWTAuthenticatorStatus `json:"status,omitempty"`
}
// List of JWTAuthenticator objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type JWTAuthenticatorList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []JWTAuthenticator `json:"items"`
}

View File

@@ -29,9 +29,11 @@ type WebhookAuthenticatorSpec struct {
// WebhookAuthenticator describes the configuration of a webhook authenticator.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=all;authenticator;authenticators
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
// +kubebuilder:subresource:status
type WebhookAuthenticator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@@ -1,8 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=config.concierge.pinniped.dev
// Package config is the internal version of the Pinniped concierge configuration API.
package config

View File

@@ -1,4 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config

View File

@@ -1,12 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

View File

@@ -3,7 +3,6 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/concierge/config
// +k8s:defaulter-gen=TypeMeta
// +groupName=config.concierge.pinniped.dev

View File

@@ -24,7 +24,7 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.

View File

@@ -67,19 +67,21 @@ type CredentialIssuerStrategy struct {
// Describes the configuration status of a Pinniped credential issuer.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped,scope=Cluster
// +kubebuilder:subresource:status
type CredentialIssuer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Status of the credential issuer.
// +optional
Status CredentialIssuerStatus `json:"status"`
}
// List of CredentialIssuer objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CredentialIssuerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

View File

@@ -27,7 +27,6 @@ type TokenCredentialRequestStatus struct {
}
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TokenCredentialRequest struct {
metav1.TypeMeta

View File

@@ -30,6 +30,7 @@ type TokenCredentialRequestStatus struct {
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TokenCredentialRequest struct {
metav1.TypeMeta `json:",inline"`

View File

@@ -1,8 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=config.supervisor.pinniped.dev
// Package config is the internal version of the Pinniped configuration API.
package config

View File

@@ -1,4 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config

View File

@@ -1,12 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

View File

@@ -24,14 +24,14 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCProvider{},
&OIDCProviderList{},
&FederationDomain{},
&FederationDomainList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -8,20 +8,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
type OIDCProviderStatusCondition string
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
const (
SuccessOIDCProviderStatusCondition = OIDCProviderStatusCondition("Success")
DuplicateOIDCProviderStatusCondition = OIDCProviderStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretOIDCProviderStatusCondition = OIDCProviderStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidOIDCProviderStatusCondition = OIDCProviderStatusCondition("Invalid")
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
)
// OIDCProviderTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
type OIDCProviderTLSSpec struct {
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
type FederationDomainTLSSpec struct {
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
// the TLS serving certificate for the HTTPS endpoints served by this OIDCProvider. When provided, the TLS Secret
// the TLS serving certificate for the HTTPS endpoints served by this FederationDomain. When provided, the TLS Secret
// named here must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use
// for TLS.
//
@@ -41,8 +41,8 @@ type OIDCProviderTLSSpec struct {
SecretName string `json:"secretName,omitempty"`
}
// OIDCProviderSpec is a struct that describes an OIDC Provider.
type OIDCProviderSpec struct {
// FederationDomainSpec is a struct that describes an OIDC Provider.
type FederationDomainSpec struct {
// Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the
// identifier that it will use for the iss claim in issued JWTs. This field will also be used as
// the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is
@@ -54,17 +54,41 @@ type OIDCProviderSpec struct {
// +kubebuilder:validation:MinLength=1
Issuer string `json:"issuer"`
// TLS configures how this OIDCProvider is served over Transport Layer Security (TLS).
// TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
// +optional
TLS *OIDCProviderTLSSpec `json:"tls,omitempty"`
TLS *FederationDomainTLSSpec `json:"tls,omitempty"`
}
// OIDCProviderStatus is a struct that describes the actual state of an OIDC Provider.
type OIDCProviderStatus struct {
// FederationDomainSecrets holds information about this OIDC Provider's secrets.
type FederationDomainSecrets struct {
// JWKS holds the name of the corev1.Secret in which this OIDC Provider's signing/verification keys are
// stored. If it is empty, then the signing/verification keys are either unknown or they don't
// exist.
// +optional
JWKS corev1.LocalObjectReference `json:"jwks,omitempty"`
// TokenSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// signing tokens is stored.
// +optional
TokenSigningKey corev1.LocalObjectReference `json:"tokenSigningKey,omitempty"`
// StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// signing state parameters is stored.
// +optional
StateSigningKey corev1.LocalObjectReference `json:"stateSigningKey,omitempty"`
// StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// encrypting state parameters is stored.
// +optional
StateEncryptionKey corev1.LocalObjectReference `json:"stateEncryptionKey,omitempty"`
}
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status OIDCProviderStatusCondition `json:"status,omitempty"`
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Message provides human-readable details about the Status.
// +optional
@@ -76,32 +100,32 @@ type OIDCProviderStatus struct {
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
// exist.
// Secrets contains information about this OIDC Provider's secrets.
// +optional
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
Secrets FederationDomainSecrets `json:"secrets,omitempty"`
}
// OIDCProvider describes the configuration of an OIDC provider.
// FederationDomain describes the configuration of an OIDC provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OIDCProvider struct {
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:subresource:status
type FederationDomain struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec of the OIDC provider.
Spec OIDCProviderSpec `json:"spec"`
Spec FederationDomainSpec `json:"spec"`
// Status of the OIDC provider.
Status OIDCProviderStatus `json:"status,omitempty"`
Status FederationDomainStatus `json:"status,omitempty"`
}
// List of OIDCProvider objects.
// List of FederationDomain objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OIDCProviderList struct {
type FederationDomainList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []OIDCProvider `json:"items"`
Items []FederationDomain `json:"items"`
}

View File

@@ -0,0 +1,11 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:defaulter-gen=TypeMeta
// +groupName=idp.supervisor.pinniped.dev
// +groupGoName=IDP
// Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity provider (IDP) API.
package v1alpha1

View File

@@ -0,0 +1,43 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "idp.supervisor.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCIdentityProvider{},
&OIDCIdentityProviderList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource.
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}

View File

@@ -0,0 +1,75 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// ConditionStatus is effectively an enum type for Condition.Status.
type ConditionStatus string
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
// can't decide if a resource is in the condition or not. In the future, we could add other
// intermediate conditions, e.g. ConditionDegraded.
const (
ConditionTrue ConditionStatus = "True"
ConditionFalse ConditionStatus = "False"
ConditionUnknown ConditionStatus = "Unknown"
)
// Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API
// version we can switch to using the upstream type.
// See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
type Condition struct {
// type of condition in CamelCase or in foo.example.com/CamelCase.
// ---
// Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be
// useful (see .node.status.conditions), the ability to deconflict is important.
// The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
// +required
// +kubebuilder:validation:Required
// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`
// +kubebuilder:validation:MaxLength=316
Type string `json:"type"`
// status of the condition, one of True, False, Unknown.
// +required
// +kubebuilder:validation:Required
// +kubebuilder:validation:Enum=True;False;Unknown
Status ConditionStatus `json:"status"`
// observedGeneration represents the .metadata.generation that the condition was set based upon.
// For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
// with respect to the current state of the instance.
// +optional
// +kubebuilder:validation:Minimum=0
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// lastTransitionTime is the last time the condition transitioned from one status to another.
// This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
// +required
// +kubebuilder:validation:Required
// +kubebuilder:validation:Type=string
// +kubebuilder:validation:Format=date-time
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
// reason contains a programmatic identifier indicating the reason for the condition's last transition.
// Producers of specific condition types may define expected values and meanings for this field,
// and whether the values are considered a guaranteed API.
// The value should be a CamelCase string.
// This field may not be empty.
// +required
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=1024
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`
Reason string `json:"reason"`
// message is a human readable message indicating details about the transition.
// This may be an empty string.
// +required
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=32768
Message string `json:"message"`
}

View File

@@ -0,0 +1,123 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type OIDCIdentityProviderPhase string
const (
// PhasePending is the default phase for newly-created OIDCIdentityProvider resources.
PhasePending OIDCIdentityProviderPhase = "Pending"
// PhaseReady is the phase for an OIDCIdentityProvider resource in a healthy state.
PhaseReady OIDCIdentityProviderPhase = "Ready"
// PhaseError is the phase for an OIDCIdentityProvider in an unhealthy state.
PhaseError OIDCIdentityProviderPhase = "Error"
)
// Status of an OIDC identity provider.
type OIDCIdentityProviderStatus struct {
// Phase summarizes the overall status of the OIDCIdentityProvider.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase OIDCIdentityProviderPhase `json:"phase,omitempty"`
// Represents the observations of an identity provider's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
// OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization
// request parameters.
type OIDCAuthorizationConfig struct {
// AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization
// request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
// +optional
AdditionalScopes []string `json:"additionalScopes,omitempty"`
}
// OIDCClaims provides a mapping from upstream claims into identities.
type OIDCClaims struct {
// Groups provides the name of the token claim that will be used to ascertain the groups to which
// an identity belongs.
// +optional
Groups string `json:"groups"`
// Username provides the name of the token claim that will be used to ascertain an identity's
// username.
// +optional
Username string `json:"username"`
}
// OIDCClient contains information about an OIDC client (e.g., client ID and client
// secret).
type OIDCClient struct {
// SecretName contains the name of a namespace-local Secret object that provides the clientID and
// clientSecret for an OIDC client. If only the SecretName is specified in an OIDCClient
// struct, then it is expected that the Secret is of type "secrets.pinniped.dev/oidc-client" with keys
// "clientID" and "clientSecret".
SecretName string `json:"secretName"`
}
// Spec for configuring an OIDC identity provider.
type OIDCIdentityProviderSpec struct {
// Issuer is the issuer URL of this OIDC identity provider, i.e., where to fetch
// /.well-known/openid-configuration.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^https://`
Issuer string `json:"issuer"`
// TLS configuration for discovery/JWKS requests to the issuer.
// +optional
TLS *TLSSpec `json:"tls,omitempty"`
// AuthorizationConfig holds information about how to form the OAuth2 authorization request
// parameters to be used with this OIDC identity provider.
// +optional
AuthorizationConfig OIDCAuthorizationConfig `json:"authorizationConfig,omitempty"`
// Claims provides the names of token claims that will be used when inspecting an identity from
// this OIDC identity provider.
// +optional
Claims OIDCClaims `json:"claims"`
// OIDCClient contains OIDC client information to be used used with this OIDC identity
// provider.
Client OIDCClient `json:"client"`
}
// OIDCIdentityProvider describes the configuration of an upstream OpenID Connect identity provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-idp;pinniped-idps
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type OIDCIdentityProvider struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec OIDCIdentityProviderSpec `json:"spec"`
// Status of the identity provider.
Status OIDCIdentityProviderStatus `json:"status,omitempty"`
}
// List of OIDCIdentityProvider objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OIDCIdentityProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []OIDCIdentityProvider `json:"items"`
}

View File

@@ -0,0 +1,11 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
// Configuration for TLS parameters related to identity provider integration.
type TLSSpec struct {
// X.509 Certificate Authority (base64-encoded PEM bundle). If omitted, a default set of system roots will be trusted.
// +optional
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package main provides a authentication webhook program.
@@ -31,13 +31,14 @@ import (
kubeinformers "k8s.io/client-go/informers"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/klog/v2"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/controller/apicerts"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/dynamiccert"
"go.pinniped.dev/internal/kubeclient"
"go.pinniped.dev/internal/plog"
)
const (
@@ -92,11 +93,11 @@ func (w *webhook) start(ctx context.Context, l net.Listener) error {
go func() {
select {
case err := <-errCh:
klog.InfoS("server exited", "err", err)
plog.Debug("server exited", "err", err)
case <-ctx.Done():
klog.InfoS("server context cancelled", "err", ctx.Err())
plog.Debug("server context cancelled", "err", ctx.Err())
if err := server.Shutdown(context.Background()); err != nil {
klog.InfoS("server shutdown failed", "err", err)
plog.Debug("server shutdown failed", "err", err)
}
}
}()
@@ -114,13 +115,13 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
secret, err := w.secretInformer.Lister().Secrets(namespace).Get(username)
notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound {
klog.InfoS("could not get secret", "err", err)
plog.Debug("could not get secret", "err", err)
rsp.WriteHeader(http.StatusInternalServerError)
return
}
if notFound {
klog.InfoS("user not found")
plog.Debug("user not found")
respondWithUnauthenticated(rsp)
return
}
@@ -130,7 +131,7 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
[]byte(password),
) == nil
if !passwordMatches {
klog.InfoS("authentication failed: wrong password")
plog.Debug("authentication failed: wrong password")
respondWithUnauthenticated(rsp)
return
}
@@ -141,32 +142,32 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
groupsCSVReader := csv.NewReader(groupsBuf)
groups, err = groupsCSVReader.Read()
if err != nil {
klog.InfoS("could not read groups", "err", err)
plog.Debug("could not read groups", "err", err)
rsp.WriteHeader(http.StatusInternalServerError)
return
}
trimLeadingAndTrailingWhitespace(groups)
}
klog.InfoS("successful authentication")
plog.Debug("successful authentication")
respondWithAuthenticated(rsp, secret.ObjectMeta.Name, string(secret.UID), groups)
}
func getUsernameAndPasswordFromRequest(rsp http.ResponseWriter, req *http.Request) (string, string, error) {
if req.URL.Path != "/authenticate" {
klog.InfoS("received request path other than /authenticate", "path", req.URL.Path)
plog.Debug("received request path other than /authenticate", "path", req.URL.Path)
rsp.WriteHeader(http.StatusNotFound)
return "", "", invalidRequest
}
if req.Method != http.MethodPost {
klog.InfoS("received request method other than post", "method", req.Method)
plog.Debug("received request method other than post", "method", req.Method)
rsp.WriteHeader(http.StatusMethodNotAllowed)
return "", "", invalidRequest
}
if !headerContains(req, "Content-Type", "application/json") {
klog.InfoS("content type is not application/json", "Content-Type", req.Header.Values("Content-Type"))
plog.Debug("content type is not application/json", "Content-Type", req.Header.Values("Content-Type"))
rsp.WriteHeader(http.StatusUnsupportedMediaType)
return "", "", invalidRequest
}
@@ -174,39 +175,39 @@ func getUsernameAndPasswordFromRequest(rsp http.ResponseWriter, req *http.Reques
if !headerContains(req, "Accept", "application/json") &&
!headerContains(req, "Accept", "application/*") &&
!headerContains(req, "Accept", "*/*") {
klog.InfoS("client does not accept application/json", "Accept", req.Header.Values("Accept"))
plog.Debug("client does not accept application/json", "Accept", req.Header.Values("Accept"))
rsp.WriteHeader(http.StatusUnsupportedMediaType)
return "", "", invalidRequest
}
if req.Body == nil {
klog.InfoS("invalid nil body")
plog.Debug("invalid nil body")
rsp.WriteHeader(http.StatusBadRequest)
return "", "", invalidRequest
}
var body authenticationv1beta1.TokenReview
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
klog.InfoS("failed to decode body", "err", err)
plog.Debug("failed to decode body", "err", err)
rsp.WriteHeader(http.StatusBadRequest)
return "", "", invalidRequest
}
if body.APIVersion != authenticationv1beta1.SchemeGroupVersion.String() {
klog.InfoS("invalid TokenReview apiVersion", "apiVersion", body.APIVersion)
plog.Debug("invalid TokenReview apiVersion", "apiVersion", body.APIVersion)
rsp.WriteHeader(http.StatusBadRequest)
return "", "", invalidRequest
}
if body.Kind != "TokenReview" {
klog.InfoS("invalid TokenReview kind", "kind", body.Kind)
plog.Debug("invalid TokenReview kind", "kind", body.Kind)
rsp.WriteHeader(http.StatusBadRequest)
return "", "", invalidRequest
}
tokenSegments := strings.SplitN(body.Spec.Token, ":", 2)
if len(tokenSegments) != 2 {
klog.InfoS("bad token format in request")
plog.Debug("bad token format in request")
rsp.WriteHeader(http.StatusBadRequest)
return "", "", invalidRequest
}
@@ -247,7 +248,7 @@ func respondWithUnauthenticated(rsp http.ResponseWriter) {
},
}
if err := json.NewEncoder(rsp).Encode(body); err != nil {
klog.InfoS("could not encode response", "err", err)
plog.Debug("could not encode response", "err", err)
rsp.WriteHeader(http.StatusInternalServerError)
}
}
@@ -273,26 +274,11 @@ func respondWithAuthenticated(
},
}
if err := json.NewEncoder(rsp).Encode(body); err != nil {
klog.InfoS("could not encode response", "err", err)
plog.Debug("could not encode response", "err", err)
rsp.WriteHeader(http.StatusInternalServerError)
}
}
func newK8sClient() (kubernetes.Interface, error) {
kubeConfig, err := restclient.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
}
// Connect to the core Kubernetes API.
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
}
return kubeClient, nil
}
func startControllers(
ctx context.Context,
dynamicCertProvider dynamiccert.Provider,
@@ -358,21 +344,21 @@ func run() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kubeClient, err := newK8sClient()
client, err := kubeclient.New()
if err != nil {
return fmt.Errorf("cannot create k8s client: %w", err)
}
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
kubeClient,
client.Kubernetes,
defaultResyncInterval,
kubeinformers.WithNamespace(namespace),
)
dynamicCertProvider := dynamiccert.New()
startControllers(ctx, dynamicCertProvider, kubeClient, kubeInformers)
klog.InfoS("controllers are ready")
startControllers(ctx, dynamicCertProvider, client.Kubernetes, kubeInformers)
plog.Debug("controllers are ready")
//nolint: gosec // Intentionally binding to all network interfaces.
l, err := net.Listen("tcp", ":8443")
@@ -385,15 +371,20 @@ func run() error {
if err != nil {
return fmt.Errorf("cannot start webhook: %w", err)
}
klog.InfoS("webhook is ready", "address", l.Addr().String())
plog.Debug("webhook is ready", "address", l.Addr().String())
gotSignal := waitForSignal()
klog.InfoS("webhook exiting", "signal", gotSignal)
plog.Debug("webhook exiting", "signal", gotSignal)
return nil
}
func main() {
// Hardcode the logging level to debug, since this is a test app and it is very helpful to have
// verbose logs to debug test failures.
if err := plog.ValidateAndSetLogLevelGlobally(plog.LevelDebug); err != nil {
klog.Fatal(err)
}
if err := run(); err != nil {
klog.Fatal(err)
}

View File

@@ -1,10 +1,11 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"crypto/rand"
"crypto/tls"
"fmt"
"net"
@@ -14,24 +15,35 @@ import (
"strings"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/clock"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/version"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"k8s.io/klog/v2/klogr"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned"
pinnipedinformers "go.pinniped.dev/generated/1.19/client/supervisor/informers/externalversions"
configv1alpha1 "go.pinniped.dev/generated/1.20/apis/supervisor/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.20/client/supervisor/clientset/versioned"
pinnipedinformers "go.pinniped.dev/generated/1.20/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/config/supervisor"
"go.pinniped.dev/internal/controller/supervisorconfig"
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorstorage"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/deploymentref"
"go.pinniped.dev/internal/downward"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/kubeclient"
"go.pinniped.dev/internal/oidc/jwks"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/oidc/provider/manager"
"go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/secret"
)
const (
@@ -50,11 +62,11 @@ func start(ctx context.Context, l net.Listener, handler http.Handler) {
go func() {
select {
case err := <-errCh:
klog.InfoS("server exited", "err", err)
plog.Debug("server exited", "err", err)
case <-ctx.Done():
klog.InfoS("server context cancelled", "err", ctx.Err())
plog.Debug("server context cancelled", "err", ctx.Err())
if err := server.Shutdown(context.Background()); err != nil {
klog.InfoS("server shutdown failed", "err", err)
plog.Debug("server shutdown failed", "err", err)
}
}
}()
@@ -66,26 +78,42 @@ func waitForSignal() os.Signal {
return <-signalCh
}
//nolint:funlen
func startControllers(
ctx context.Context,
cfg *supervisor.Config,
issuerManager *manager.Manager,
dynamicJWKSProvider jwks.DynamicJWKSProvider,
dynamicTLSCertProvider provider.DynamicTLSCertProvider,
dynamicUpstreamIDPProvider provider.DynamicUpstreamIDPProvider,
secretCache *secret.Cache,
supervisorDeployment *appsv1.Deployment,
kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface,
kubeInformers kubeinformers.SharedInformerFactory,
pinnipedInformers pinnipedinformers.SharedInformerFactory,
) {
federationDomainInformer := pinnipedInformers.Config().V1alpha1().FederationDomains()
secretInformer := kubeInformers.Core().V1().Secrets()
// Create controller manager.
controllerManager := controllerlib.
NewManager().
WithController(
supervisorconfig.NewOIDCProviderWatcherController(
supervisorstorage.GarbageCollectorController(
clock.RealClock{},
kubeClient,
secretInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
supervisorconfig.NewFederationDomainWatcherController(
issuerManager,
clock.RealClock{},
pinnipedClient,
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
@@ -95,8 +123,8 @@ func startControllers(
cfg.Labels,
kubeClient,
pinnipedClient,
kubeInformers.Core().V1().Secrets(),
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
@@ -104,8 +132,8 @@ func startControllers(
WithController(
supervisorconfig.NewJWKSObserverController(
dynamicJWKSProvider,
kubeInformers.Core().V1().Secrets(),
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
@@ -114,12 +142,106 @@ func startControllers(
supervisorconfig.NewTLSCertObserverController(
dynamicTLSCertProvider,
cfg.NamesConfig.DefaultTLSCertificateSecret,
kubeInformers.Core().V1().Secrets(),
pinnipedInformers.Config().V1alpha1().OIDCProviders(),
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
)
).
WithController(
generator.NewSupervisorSecretsController(
supervisorDeployment,
cfg.Labels,
kubeClient,
secretInformer,
func(secret []byte) {
plog.Debug("setting csrf cookie secret")
secretCache.SetCSRFCookieEncoderHashKey(secret)
},
controllerlib.WithInformer,
controllerlib.WithInitialEvent,
),
singletonWorker,
).
WithController(
generator.NewFederationDomainSecretsController(
generator.NewSymmetricSecretHelper(
"pinniped-oidc-provider-hmac-key-",
cfg.Labels,
rand.Reader,
generator.SecretUsageTokenSigningKey,
func(federationDomainIssuer string, symmetricKey []byte) {
plog.Debug("setting hmac secret", "issuer", federationDomainIssuer)
secretCache.SetTokenHMACKey(federationDomainIssuer, symmetricKey)
},
),
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
return &fd.Secrets.TokenSigningKey
},
kubeClient,
pinnipedClient,
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
generator.NewFederationDomainSecretsController(
generator.NewSymmetricSecretHelper(
"pinniped-oidc-provider-upstream-state-signature-key-",
cfg.Labels,
rand.Reader,
generator.SecretUsageStateSigningKey,
func(federationDomainIssuer string, symmetricKey []byte) {
plog.Debug("setting state signature key", "issuer", federationDomainIssuer)
secretCache.SetStateEncoderHashKey(federationDomainIssuer, symmetricKey)
},
),
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
return &fd.Secrets.StateSigningKey
},
kubeClient,
pinnipedClient,
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
generator.NewFederationDomainSecretsController(
generator.NewSymmetricSecretHelper(
"pinniped-oidc-provider-upstream-state-encryption-key-",
cfg.Labels,
rand.Reader,
generator.SecretUsageStateEncryptionKey,
func(federationDomainIssuer string, symmetricKey []byte) {
plog.Debug("setting state encryption key", "issuer", federationDomainIssuer)
secretCache.SetStateEncoderBlockKey(federationDomainIssuer, symmetricKey)
},
),
func(fd *configv1alpha1.FederationDomainStatus) *corev1.LocalObjectReference {
return &fd.Secrets.StateEncryptionKey
},
kubeClient,
pinnipedClient,
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
upstreamwatcher.New(
dynamicUpstreamIDPProvider,
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
secretInformer,
klogr.New(),
controllerlib.WithInformer,
),
singletonWorker)
kubeInformers.Start(ctx.Done())
pinnipedInformers.Start(ctx.Done())
@@ -131,44 +253,33 @@ func startControllers(
go controllerManager.Start(ctx)
}
func newClients() (kubernetes.Interface, pinnipedclientset.Interface, error) {
kubeConfig, err := restclient.InClusterConfig()
if err != nil {
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
}
func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
serverInstallationNamespace := podInfo.Namespace
// Connect to the core Kubernetes API.
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return nil, nil, fmt.Errorf("could not create kube client: %w", err)
}
// Connect to the Pinniped API.
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
if err != nil {
return nil, nil, fmt.Errorf("could not create pinniped client: %w", err)
}
return kubeClient, pinnipedClient, nil
}
func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
kubeClient, pinnipedClient, err := newClients()
dref, supervisorDeployment, err := deploymentref.New(podInfo)
if err != nil {
return fmt.Errorf("cannot create deployment ref: %w", err)
}
client, err := kubeclient.New(
dref,
kubeclient.WithMiddleware(groupsuffix.New(*cfg.APIGroupSuffix)),
)
if err != nil {
return fmt.Errorf("cannot create k8s client: %w", err)
}
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
kubeClient,
client.Kubernetes,
defaultResyncInterval,
kubeinformers.WithNamespace(serverInstallationNamespace),
)
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
pinnipedClient,
client.PinnipedSupervisor,
defaultResyncInterval,
pinnipedinformers.WithNamespace(serverInstallationNamespace),
)
@@ -181,9 +292,17 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
dynamicJWKSProvider := jwks.NewDynamicJWKSProvider()
dynamicTLSCertProvider := provider.NewDynamicTLSCertProvider()
dynamicUpstreamIDPProvider := provider.NewDynamicUpstreamIDPProvider()
secretCache := secret.Cache{}
// OIDC endpoints will be served by the oidProvidersManager, and any non-OIDC paths will fallback to the healthMux.
oidProvidersManager := manager.NewManager(healthMux, dynamicJWKSProvider)
oidProvidersManager := manager.NewManager(
healthMux,
dynamicJWKSProvider,
dynamicUpstreamIDPProvider,
&secretCache,
client.Kubernetes.CoreV1().Secrets(serverInstallationNamespace),
)
startControllers(
ctx,
@@ -191,8 +310,11 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
oidProvidersManager,
dynamicJWKSProvider,
dynamicTLSCertProvider,
kubeClient,
pinnipedClient,
dynamicUpstreamIDPProvider,
&secretCache,
supervisorDeployment,
client.Kubernetes,
client.PinnipedSupervisor,
kubeInformers,
pinnipedInformers,
)
@@ -211,7 +333,7 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
defaultCert := dynamicTLSCertProvider.GetDefaultTLSCert()
klog.InfoS("GetCertificate called for port 8443",
plog.Debug("GetCertificate called for port 8443",
"info.ServerName", info.ServerName,
"foundSNICert", cert != nil,
"foundDefaultCert", defaultCert != nil,
@@ -228,13 +350,13 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
defer func() { _ = httpsListener.Close() }()
start(ctx, httpsListener, oidProvidersManager)
klog.InfoS("supervisor is ready",
plog.Debug("supervisor is ready",
"httpAddress", httpListener.Addr().String(),
"httpsAddress", httpsListener.Addr().String(),
)
gotSignal := waitForSignal()
klog.InfoS("supervisor exiting", "signal", gotSignal)
plog.Debug("supervisor exiting", "signal", gotSignal)
return nil
}
@@ -242,6 +364,7 @@ func run(serverInstallationNamespace string, cfg *supervisor.Config) error {
func main() {
logs.InitLogs()
defer logs.FlushLogs()
plog.RemoveKlogGlobalFlags() // move this whenever the below code gets refactored to use cobra
klog.Infof("Running %s at %#v", rest.DefaultKubernetesUserAgent(), version.Get())
klog.Infof("Command-line arguments were: %s %s %s", os.Args[0], os.Args[1], os.Args[2])
@@ -258,7 +381,7 @@ func main() {
klog.Fatal(fmt.Errorf("could not load config: %w", err))
}
if err := run(podInfo.Namespace, cfg); err != nil {
if err := run(podInfo, cfg); err != nil {
klog.Fatal(err)
}
}

View File

@@ -22,3 +22,9 @@ func mustMarkHidden(cmd *cobra.Command, flags ...string) {
}
}
}
func mustMarkDeprecated(cmd *cobra.Command, flag, usageMessage string) {
if err := cmd.Flags().MarkDeprecated(flag, usageMessage); err != nil {
panic(err)
}
}

View File

@@ -1,167 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
auth1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
"go.pinniped.dev/internal/client"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/here"
)
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(newExchangeCredentialCmd(os.Args, os.Stdout, os.Stderr).cmd)
}
type exchangeCredentialCommand struct {
// runFunc is called by the cobra.Command.Run hook. It is included here for
// testability.
runFunc func(stdout, stderr io.Writer)
// cmd is the cobra.Command for this CLI command. It is included here for
// testability.
cmd *cobra.Command
}
func newExchangeCredentialCmd(args []string, stdout, stderr io.Writer) *exchangeCredentialCommand {
c := &exchangeCredentialCommand{
runFunc: runExchangeCredential,
}
c.cmd = &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
c.runFunc(stdout, stderr)
},
Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "exchange-credential",
Short: "Exchange a credential for a cluster-specific access credential",
Long: here.Doc(`
Exchange a credential which proves your identity for a time-limited,
cluster-specific access credential.
Designed to be conveniently used as an credential plugin for kubectl.
See the help message for 'pinniped get-kubeconfig' for more
information about setting up a kubeconfig file using Pinniped.
Requires all of the following environment variables, which are
typically set in the kubeconfig:
- PINNIPED_TOKEN: the token to send to Pinniped for exchange
- PINNIPED_NAMESPACE: the namespace of the authenticator to authenticate
against
- PINNIPED_AUTHENTICATOR_TYPE: the type of authenticator to authenticate
against (e.g., "webhook")
- PINNIPED_AUTHENTICATOR_NAME: the name of the authenticator to authenticate
against
- PINNIPED_CA_BUNDLE: the CA bundle to trust when calling
Pinniped's HTTPS endpoint
- PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential
exchange API
For more information about credential plugins in general, see
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
`),
}
c.cmd.SetArgs(args)
c.cmd.SetOut(stdout)
c.cmd.SetErr(stderr)
return c
}
type envGetter func(string) (string, bool)
type tokenExchanger func(
ctx context.Context,
namespace string,
authenticator corev1.TypedLocalObjectReference,
token string,
caBundle string,
apiEndpoint string,
) (*clientauthenticationv1beta1.ExecCredential, error)
const (
ErrMissingEnvVar = constable.Error("failed to get credential: environment variable not set")
ErrInvalidAuthenticatorType = constable.Error("invalid authenticator type")
)
func runExchangeCredential(stdout, _ io.Writer) {
err := exchangeCredential(os.LookupEnv, client.ExchangeToken, stdout, 30*time.Second)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)
}
}
func exchangeCredential(envGetter envGetter, tokenExchanger tokenExchanger, outputWriter io.Writer, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
namespace, varExists := envGetter("PINNIPED_NAMESPACE")
if !varExists {
return envVarNotSetError("PINNIPED_NAMESPACE")
}
authenticatorType, varExists := envGetter("PINNIPED_AUTHENTICATOR_TYPE")
if !varExists {
return envVarNotSetError("PINNIPED_AUTHENTICATOR_TYPE")
}
authenticatorName, varExists := envGetter("PINNIPED_AUTHENTICATOR_NAME")
if !varExists {
return envVarNotSetError("PINNIPED_AUTHENTICATOR_NAME")
}
token, varExists := envGetter("PINNIPED_TOKEN")
if !varExists {
return envVarNotSetError("PINNIPED_TOKEN")
}
caBundle, varExists := envGetter("PINNIPED_CA_BUNDLE")
if !varExists {
return envVarNotSetError("PINNIPED_CA_BUNDLE")
}
apiEndpoint, varExists := envGetter("PINNIPED_K8S_API_ENDPOINT")
if !varExists {
return envVarNotSetError("PINNIPED_K8S_API_ENDPOINT")
}
authenticator := corev1.TypedLocalObjectReference{Name: authenticatorName}
switch strings.ToLower(authenticatorType) {
case "webhook":
authenticator.APIGroup = &auth1alpha1.SchemeGroupVersion.Group
authenticator.Kind = "WebhookAuthenticator"
default:
return fmt.Errorf(`%w: %q, supported values are "webhook"`, ErrInvalidAuthenticatorType, authenticatorType)
}
cred, err := tokenExchanger(ctx, namespace, authenticator, token, caBundle, apiEndpoint)
if err != nil {
return fmt.Errorf("failed to get credential: %w", err)
}
err = json.NewEncoder(outputWriter).Encode(cred)
if err != nil {
return fmt.Errorf("failed to marshal response to stdout: %w", err)
}
return nil
}
func envVarNotSetError(varName string) error {
return fmt.Errorf("%w: %s", ErrMissingEnvVar, varName)
}

View File

@@ -1,296 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"context"
"fmt"
"io"
"testing"
"time"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
)
var (
knownGoodUsageForExchangeCredential = here.Doc(`
Usage:
exchange-credential [flags]
Flags:
-h, --help help for exchange-credential
`)
knownGoodHelpForExchangeCredential = here.Doc(`
Exchange a credential which proves your identity for a time-limited,
cluster-specific access credential.
Designed to be conveniently used as an credential plugin for kubectl.
See the help message for 'pinniped get-kubeconfig' for more
information about setting up a kubeconfig file using Pinniped.
Requires all of the following environment variables, which are
typically set in the kubeconfig:
- PINNIPED_TOKEN: the token to send to Pinniped for exchange
- PINNIPED_NAMESPACE: the namespace of the authenticator to authenticate
against
- PINNIPED_AUTHENTICATOR_TYPE: the type of authenticator to authenticate
against (e.g., "webhook")
- PINNIPED_AUTHENTICATOR_NAME: the name of the authenticator to authenticate
against
- PINNIPED_CA_BUNDLE: the CA bundle to trust when calling
Pinniped's HTTPS endpoint
- PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential
exchange API
For more information about credential plugins in general, see
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
Usage:
exchange-credential [flags]
Flags:
-h, --help help for exchange-credential
`)
)
func TestNewCredentialExchangeCmd(t *testing.T) {
spec.Run(t, "newCredentialExchangeCmd", func(t *testing.T, when spec.G, it spec.S) {
var r *require.Assertions
var stdout, stderr *bytes.Buffer
it.Before(func() {
r = require.New(t)
stdout, stderr = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
})
it("calls runFunc and does not print usage or help when correct arguments and flags are used", func() {
c := newExchangeCredentialCmd([]string{}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
r.NoError(c.cmd.Execute())
r.True(runFuncCalled)
r.Empty(stdout.String())
r.Empty(stderr.String())
})
it("fails when args are passed", func() {
c := newExchangeCredentialCmd([]string{"some-arg"}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
errorMessage := `unknown command "some-arg" for "exchange-credential"`
r.EqualError(c.cmd.Execute(), errorMessage)
r.False(runFuncCalled)
output := "Error: " + errorMessage + "\n" + knownGoodUsageForExchangeCredential
r.Equal(output, stdout.String())
r.Empty(stderr.String())
})
it("prints a nice help message", func() {
c := newExchangeCredentialCmd([]string{"--help"}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
r.NoError(c.cmd.Execute())
r.False(runFuncCalled)
r.Equal(knownGoodHelpForExchangeCredential, stdout.String())
r.Empty(stderr.String())
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}
func TestExchangeCredential(t *testing.T) {
spec.Run(t, "cmd.exchangeCredential", func(t *testing.T, when spec.G, it spec.S) {
var r *require.Assertions
var buffer *bytes.Buffer
var tokenExchanger tokenExchanger
var fakeEnv map[string]string
var envGetter envGetter = func(envVarName string) (string, bool) {
value, present := fakeEnv[envVarName]
if !present {
return "", false
}
return value, true
}
it.Before(func() {
r = require.New(t)
buffer = new(bytes.Buffer)
fakeEnv = map[string]string{
"PINNIPED_NAMESPACE": "namespace from env",
"PINNIPED_AUTHENTICATOR_TYPE": "Webhook",
"PINNIPED_AUTHENTICATOR_NAME": "webhook name from env",
"PINNIPED_TOKEN": "token from env",
"PINNIPED_CA_BUNDLE": "ca bundle from env",
"PINNIPED_K8S_API_ENDPOINT": "k8s api from env",
}
})
when("env vars are missing", func() {
it("returns an error when PINNIPED_NAMESPACE is missing", func() {
delete(fakeEnv, "PINNIPED_NAMESPACE")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_NAMESPACE")
})
it("returns an error when PINNIPED_AUTHENTICATOR_TYPE is missing", func() {
delete(fakeEnv, "PINNIPED_AUTHENTICATOR_TYPE")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_AUTHENTICATOR_TYPE")
})
it("returns an error when PINNIPED_AUTHENTICATOR_NAME is missing", func() {
delete(fakeEnv, "PINNIPED_AUTHENTICATOR_NAME")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_AUTHENTICATOR_NAME")
})
it("returns an error when PINNIPED_TOKEN is missing", func() {
delete(fakeEnv, "PINNIPED_TOKEN")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_TOKEN")
})
it("returns an error when PINNIPED_CA_BUNDLE is missing", func() {
delete(fakeEnv, "PINNIPED_CA_BUNDLE")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_CA_BUNDLE")
})
it("returns an error when PINNIPED_K8S_API_ENDPOINT is missing", func() {
delete(fakeEnv, "PINNIPED_K8S_API_ENDPOINT")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_K8S_API_ENDPOINT")
})
})
when("env vars are invalid", func() {
it("returns an error when PINNIPED_AUTHENTICATOR_TYPE is missing", func() {
fakeEnv["PINNIPED_AUTHENTICATOR_TYPE"] = "invalid"
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, `invalid authenticator type: "invalid", supported values are "webhook"`)
})
})
when("the token exchange fails", func() {
it.Before(func() {
tokenExchanger = func(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
return nil, fmt.Errorf("some error")
}
})
it("returns an error", func() {
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: some error")
})
})
when("the JSON encoder fails", func() {
it.Before(func() {
tokenExchanger = func(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
return &clientauthenticationv1beta1.ExecCredential{
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
Token: "some token",
},
}, nil
}
})
it("returns an error", func() {
err := exchangeCredential(envGetter, tokenExchanger, &testutil.ErrorWriter{ReturnError: fmt.Errorf("some IO error")}, 30*time.Second)
r.EqualError(err, "failed to marshal response to stdout: some IO error")
})
})
when("the token exchange times out", func() {
it.Before(func() {
tokenExchanger = func(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
select {
case <-time.After(100 * time.Millisecond):
return &clientauthenticationv1beta1.ExecCredential{
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
Token: "some token",
},
}, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
})
it("returns an error", func() {
err := exchangeCredential(envGetter, tokenExchanger, buffer, 1*time.Millisecond)
r.EqualError(err, "failed to get credential: context deadline exceeded")
})
})
when("the token exchange succeeds", func() {
var actualNamespace, actualToken, actualCaBundle, actualAPIEndpoint string
it.Before(func() {
tokenExchanger = func(ctx context.Context, namespace string, authenticator corev1.TypedLocalObjectReference, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) {
actualNamespace, actualToken, actualCaBundle, actualAPIEndpoint = namespace, token, caBundle, apiEndpoint
now := metav1.NewTime(time.Date(2020, 7, 29, 1, 2, 3, 0, time.UTC))
return &clientauthenticationv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
ExpirationTimestamp: &now,
ClientCertificateData: "some certificate",
ClientKeyData: "some key",
Token: "some token",
},
}, nil
}
})
it("writes the execCredential to the given writer", func() {
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.NoError(err)
r.Equal(fakeEnv["PINNIPED_NAMESPACE"], actualNamespace)
r.Equal(fakeEnv["PINNIPED_TOKEN"], actualToken)
r.Equal(fakeEnv["PINNIPED_CA_BUNDLE"], actualCaBundle)
r.Equal(fakeEnv["PINNIPED_K8S_API_ENDPOINT"], actualAPIEndpoint)
expected := `{
"kind": "ExecCredential",
"apiVersion": "client.authentication.k8s.io/v1beta1",
"spec": {},
"status": {
"expirationTimestamp":"2020-07-29T01:02:03Z",
"clientCertificateData": "some certificate",
"clientKeyData":"some key",
"token": "some token"
}
}`
r.JSONEq(expected, buffer.String())
})
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}

16
cmd/pinniped/cmd/get.go Normal file
View File

@@ -0,0 +1,16 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"github.com/spf13/cobra"
)
//nolint: gochecknoglobals
var getCmd = &cobra.Command{Use: "get", Short: "get"}
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(getCmd)
}

View File

@@ -1,344 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"os"
"time"
"github.com/ghodss/yaml"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
v1 "k8s.io/client-go/tools/clientcmd/api/v1"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/here"
)
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(newGetKubeConfigCommand().Command())
}
type getKubeConfigFlags struct {
token string
kubeconfig string
contextOverride string
namespace string
authenticatorName string
authenticatorType string
}
type getKubeConfigCommand struct {
flags getKubeConfigFlags
// Test mocking points
getPathToSelf func() (string, error)
kubeClientCreator func(restConfig *rest.Config) (pinnipedclientset.Interface, error)
}
func newGetKubeConfigCommand() *getKubeConfigCommand {
return &getKubeConfigCommand{
flags: getKubeConfigFlags{
namespace: "pinniped",
},
getPathToSelf: os.Executable,
kubeClientCreator: func(restConfig *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedclientset.NewForConfig(restConfig)
},
}
}
func (c *getKubeConfigCommand) Command() *cobra.Command {
cmd := &cobra.Command{
RunE: c.run,
Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "get-kubeconfig",
Short: "Print a kubeconfig for authenticating into a cluster via Pinniped",
Long: here.Doc(`
Print a kubeconfig for authenticating into a cluster via Pinniped.
Requires admin-like access to the cluster using the current
kubeconfig context in order to access Pinniped's metadata.
The current kubeconfig is found similar to how kubectl finds it:
using the value of the --kubeconfig option, or if that is not
specified then from the value of the KUBECONFIG environment
variable, or if that is not specified then it defaults to
.kube/config in your home directory.
Prints a kubeconfig which is suitable to access the cluster using
Pinniped as the authentication mechanism. This kubeconfig output
can be saved to a file and used with future kubectl commands, e.g.:
pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
`),
}
cmd.Flags().StringVar(&c.flags.token, "token", "", "Credential to include in the resulting kubeconfig output (Required)")
cmd.Flags().StringVar(&c.flags.kubeconfig, "kubeconfig", c.flags.kubeconfig, "Path to the kubeconfig file")
cmd.Flags().StringVar(&c.flags.contextOverride, "kubeconfig-context", c.flags.contextOverride, "Kubeconfig context override")
cmd.Flags().StringVar(&c.flags.namespace, "pinniped-namespace", c.flags.namespace, "Namespace in which Pinniped was installed")
cmd.Flags().StringVar(&c.flags.authenticatorType, "authenticator-type", c.flags.authenticatorType, "Authenticator type (e.g., 'webhook')")
cmd.Flags().StringVar(&c.flags.authenticatorName, "authenticator-name", c.flags.authenticatorType, "Authenticator name")
mustMarkRequired(cmd, "token")
return cmd
}
func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
fullPathToSelf, err := c.getPathToSelf()
if err != nil {
return fmt.Errorf("could not find path to self: %w", err)
}
clientConfig := newClientConfig(c.flags.kubeconfig, c.flags.contextOverride)
currentKubeConfig, err := clientConfig.RawConfig()
if err != nil {
return err
}
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return err
}
clientset, err := c.kubeClientCreator(restConfig)
if err != nil {
return err
}
authenticatorType, authenticatorName := c.flags.authenticatorType, c.flags.authenticatorName
if authenticatorType == "" || authenticatorName == "" {
authenticatorType, authenticatorName, err = getDefaultAuthenticator(clientset, c.flags.namespace)
if err != nil {
return err
}
}
credentialIssuer, err := fetchPinnipedCredentialIssuer(clientset, c.flags.namespace)
if err != nil {
return err
}
if credentialIssuer.Status.KubeConfigInfo == nil {
return constable.Error(`CredentialIssuer "pinniped-config" was missing KubeConfigInfo`)
}
v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, c.flags.contextOverride)
if err != nil {
return err
}
err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuer, cmd.ErrOrStderr())
if err != nil {
return err
}
config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, c.flags.token, c.flags.namespace, authenticatorType, authenticatorName)
err = writeConfigAsYAML(cmd.OutOrStdout(), config)
if err != nil {
return err
}
return nil
}
func issueWarningForNonMatchingServerOrCA(v1Cluster v1.Cluster, credentialIssuer *configv1alpha1.CredentialIssuer, warningsWriter io.Writer) error {
credentialIssuerCA, err := base64.StdEncoding.DecodeString(credentialIssuer.Status.KubeConfigInfo.CertificateAuthorityData)
if err != nil {
return err
}
if v1Cluster.Server != credentialIssuer.Status.KubeConfigInfo.Server ||
!bytes.Equal(v1Cluster.CertificateAuthorityData, credentialIssuerCA) {
_, err := warningsWriter.Write([]byte("WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuer on the cluster. Using local kubeconfig values.\n"))
if err != nil {
return fmt.Errorf("output write error: %w", err)
}
}
return nil
}
type noAuthenticatorError struct{ Namespace string }
func (e noAuthenticatorError) Error() string {
return fmt.Sprintf(`no authenticators were found in namespace %q`, e.Namespace)
}
type indeterminateAuthenticatorError struct{ Namespace string }
func (e indeterminateAuthenticatorError) Error() string {
return fmt.Sprintf(
`multiple authenticators were found in namespace %q, so --authenticator-name/--authenticator-type must be specified`,
e.Namespace,
)
}
func getDefaultAuthenticator(clientset pinnipedclientset.Interface, namespace string) (string, string, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
webhooks, err := clientset.AuthenticationV1alpha1().WebhookAuthenticators(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return "", "", err
}
type ref struct{ authenticatorType, authenticatorName string }
authenticators := make([]ref, 0, len(webhooks.Items))
for _, webhook := range webhooks.Items {
authenticators = append(authenticators, ref{authenticatorType: "webhook", authenticatorName: webhook.Name})
}
if len(authenticators) == 0 {
return "", "", noAuthenticatorError{namespace}
}
if len(authenticators) > 1 {
return "", "", indeterminateAuthenticatorError{namespace}
}
return authenticators[0].authenticatorType, authenticators[0].authenticatorName, nil
}
func fetchPinnipedCredentialIssuer(clientset pinnipedclientset.Interface, pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuer, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
credentialIssuers, err := clientset.ConfigV1alpha1().CredentialIssuers(pinnipedInstallationNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
if len(credentialIssuers.Items) == 0 {
return nil, constable.Error(fmt.Sprintf(
`No CredentialIssuer was found in namespace "%s". Is Pinniped installed on this cluster in namespace "%s"?`,
pinnipedInstallationNamespace,
pinnipedInstallationNamespace,
))
}
if len(credentialIssuers.Items) > 1 {
return nil, constable.Error(fmt.Sprintf(
`More than one CredentialIssuer was found in namespace "%s"`,
pinnipedInstallationNamespace,
))
}
return &credentialIssuers.Items[0], nil
}
func newClientConfig(kubeconfigPathOverride string, currentContextName string) clientcmd.ClientConfig {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.ExplicitPath = kubeconfigPathOverride
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
CurrentContext: currentContextName,
})
return clientConfig
}
func writeConfigAsYAML(outputWriter io.Writer, config v1.Config) error {
output, err := yaml.Marshal(&config)
if err != nil {
return fmt.Errorf("YAML serialization error: %w", err)
}
_, err = outputWriter.Write(output)
if err != nil {
return fmt.Errorf("output write error: %w", err)
}
return nil
}
func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Config, currentContextNameOverride string) (v1.Cluster, error) {
v1Cluster := v1.Cluster{}
contextName := currentKubeConfig.CurrentContext
if currentContextNameOverride != "" {
contextName = currentContextNameOverride
}
err := v1.Convert_api_Cluster_To_v1_Cluster(
currentKubeConfig.Clusters[currentKubeConfig.Contexts[contextName].Cluster],
&v1Cluster,
nil,
)
if err != nil {
return v1.Cluster{}, err
}
return v1Cluster, nil
}
func newPinnipedKubeconfig(v1Cluster v1.Cluster, fullPathToSelf string, token string, namespace string, authenticatorType string, authenticatorName string) v1.Config {
clusterName := "pinniped-cluster"
userName := "pinniped-user"
return v1.Config{
Kind: "Config",
APIVersion: v1.SchemeGroupVersion.Version,
Preferences: v1.Preferences{},
Clusters: []v1.NamedCluster{
{
Name: clusterName,
Cluster: v1Cluster,
},
},
Contexts: []v1.NamedContext{
{
Name: clusterName,
Context: v1.Context{
Cluster: clusterName,
AuthInfo: userName,
},
},
},
AuthInfos: []v1.NamedAuthInfo{
{
Name: userName,
AuthInfo: v1.AuthInfo{
Exec: &v1.ExecConfig{
Command: fullPathToSelf,
Args: []string{"exchange-credential"},
Env: []v1.ExecEnvVar{
{
Name: "PINNIPED_K8S_API_ENDPOINT",
Value: v1Cluster.Server,
},
{
Name: "PINNIPED_CA_BUNDLE",
Value: string(v1Cluster.CertificateAuthorityData)},
{
Name: "PINNIPED_NAMESPACE",
Value: namespace,
},
{
Name: "PINNIPED_TOKEN",
Value: token,
},
{
Name: "PINNIPED_AUTHENTICATOR_TYPE",
Value: authenticatorType,
},
{
Name: "PINNIPED_AUTHENTICATOR_NAME",
Value: authenticatorName,
},
},
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
InstallHint: "The Pinniped CLI is required to authenticate to the current cluster.\n" +
"For more information, please visit https://pinniped.dev",
},
},
},
},
CurrentContext: clusterName,
}
}

View File

@@ -1,401 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"encoding/base64"
"fmt"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
coretesting "k8s.io/client-go/testing"
authv1alpha "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
pinnipedfake "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/fake"
"go.pinniped.dev/internal/here"
)
var (
knownGoodUsageForGetKubeConfig = here.Doc(`
Usage:
get-kubeconfig [flags]
Flags:
--authenticator-name string Authenticator name
--authenticator-type string Authenticator type (e.g., 'webhook')
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
`)
knownGoodHelpForGetKubeConfig = here.Doc(`
Print a kubeconfig for authenticating into a cluster via Pinniped.
Requires admin-like access to the cluster using the current
kubeconfig context in order to access Pinniped's metadata.
The current kubeconfig is found similar to how kubectl finds it:
using the value of the --kubeconfig option, or if that is not
specified then from the value of the KUBECONFIG environment
variable, or if that is not specified then it defaults to
.kube/config in your home directory.
Prints a kubeconfig which is suitable to access the cluster using
Pinniped as the authentication mechanism. This kubeconfig output
can be saved to a file and used with future kubectl commands, e.g.:
pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
Usage:
get-kubeconfig [flags]
Flags:
--authenticator-name string Authenticator name
--authenticator-type string Authenticator type (e.g., 'webhook')
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
`)
)
func TestNewGetKubeConfigCmd(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args []string
wantError bool
wantStdout string
wantStderr string
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: knownGoodHelpForGetKubeConfig,
},
{
name: "missing required flag",
args: []string{},
wantError: true,
wantStdout: `Error: required flag(s) "token" not set` + "\n" + knownGoodUsageForGetKubeConfig,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cmd := newGetKubeConfigCommand().Command()
require.NotNil(t, cmd)
var stdout, stderr bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if tt.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
})
}
}
type expectedKubeconfigYAML struct {
clusterCAData string
clusterServer string
command string
token string
pinnipedEndpoint string
pinnipedCABundle string
namespace string
authenticatorType string
authenticatorName string
}
func (e expectedKubeconfigYAML) String() string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: %s
server: %s
name: pinniped-cluster
contexts:
- context:
cluster: pinniped-cluster
user: pinniped-user
name: pinniped-cluster
current-context: pinniped-cluster
kind: Config
preferences: {}
users:
- name: pinniped-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- exchange-credential
command: %s
env:
- name: PINNIPED_K8S_API_ENDPOINT
value: %s
- name: PINNIPED_CA_BUNDLE
value: %s
- name: PINNIPED_NAMESPACE
value: %s
- name: PINNIPED_TOKEN
value: %s
- name: PINNIPED_AUTHENTICATOR_TYPE
value: %s
- name: PINNIPED_AUTHENTICATOR_NAME
value: %s
installHint: |-
The Pinniped CLI is required to authenticate to the current cluster.
For more information, please visit https://pinniped.dev
`, e.clusterCAData, e.clusterServer, e.command, e.pinnipedEndpoint, e.pinnipedCABundle, e.namespace, e.token, e.authenticatorType, e.authenticatorName)
}
func newCredentialIssuer(name, namespace, server, certificateAuthorityData string) *configv1alpha1.CredentialIssuer {
return &configv1alpha1.CredentialIssuer{
TypeMeta: metav1.TypeMeta{
Kind: "CredentialIssuer",
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Status: configv1alpha1.CredentialIssuerStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerKubeConfigInfo{
Server: server,
CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte(certificateAuthorityData)),
},
},
}
}
func TestRun(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mocks func(*getKubeConfigCommand)
wantError string
wantStdout string
wantStderr string
}{
{
name: "failure to get path to self",
mocks: func(cmd *getKubeConfigCommand) {
cmd.getPathToSelf = func() (string, error) {
return "", fmt.Errorf("some error getting path to self")
}
},
wantError: "could not find path to self: some error getting path to self",
},
{
name: "kubeconfig does not exist",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.kubeconfig = "./testdata/does-not-exist.yaml"
},
wantError: "stat ./testdata/does-not-exist.yaml: no such file or directory",
},
{
name: "fail to get client",
mocks: func(cmd *getKubeConfigCommand) {
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return nil, fmt.Errorf("some error configuring clientset")
}
},
wantError: "some error configuring clientset",
},
{
name: "fail to get authenticators",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.authenticatorName = ""
cmd.flags.authenticatorType = ""
clientset := pinnipedfake.NewSimpleClientset()
clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some error getting authenticators")
})
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return clientset, nil
}
},
wantError: "some error getting authenticators",
},
{
name: "zero authenticators",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.authenticatorName = ""
cmd.flags.authenticatorType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(), nil
}
},
wantError: `no authenticators were found in namespace "test-namespace"`,
},
{
name: "multiple authenticators",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.authenticatorName = ""
cmd.flags.authenticatorType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
&authv1alpha.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-one"}},
&authv1alpha.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-two"}},
), nil
}
},
wantError: `multiple authenticators were found in namespace "test-namespace", so --authenticator-name/--authenticator-type must be specified`,
},
{
name: "fail to get CredentialIssuers",
mocks: func(cmd *getKubeConfigCommand) {
clientset := pinnipedfake.NewSimpleClientset()
clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some error getting CredentialIssuers")
})
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return clientset, nil
}
},
wantError: "some error getting CredentialIssuers",
},
{
name: "zero CredentialIssuers found",
mocks: func(cmd *getKubeConfigCommand) {
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
newCredentialIssuer("pinniped-config-1", "not-the-test-namespace", "", ""),
), nil
}
},
wantError: `No CredentialIssuer was found in namespace "test-namespace". Is Pinniped installed on this cluster in namespace "test-namespace"?`,
},
{
name: "multiple CredentialIssuers found",
mocks: func(cmd *getKubeConfigCommand) {
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
newCredentialIssuer("pinniped-config-1", "test-namespace", "", ""),
newCredentialIssuer("pinniped-config-2", "test-namespace", "", ""),
), nil
}
},
wantError: `More than one CredentialIssuer was found in namespace "test-namespace"`,
},
{
name: "CredentialIssuer missing KubeConfigInfo",
mocks: func(cmd *getKubeConfigCommand) {
ci := newCredentialIssuer("pinniped-config", "test-namespace", "", "")
ci.Status.KubeConfigInfo = nil
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(ci), nil
}
},
wantError: `CredentialIssuer "pinniped-config" was missing KubeConfigInfo`,
},
{
name: "KubeConfigInfo has invalid base64",
mocks: func(cmd *getKubeConfigCommand) {
ci := newCredentialIssuer("pinniped-config", "test-namespace", "https://example.com", "")
ci.Status.KubeConfigInfo.CertificateAuthorityData = "invalid-base64-test-ca"
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(ci), nil
}
},
wantError: `illegal base64 data at input byte 7`,
},
{
name: "success using remote CA data",
mocks: func(cmd *getKubeConfigCommand) {
ci := newCredentialIssuer("pinniped-config", "test-namespace", "https://fake-server-url-value", "fake-certificate-authority-data-value")
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(ci), nil
}
},
wantStdout: expectedKubeconfigYAML{
clusterCAData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
clusterServer: "https://fake-server-url-value",
command: "/path/to/pinniped",
token: "test-token",
pinnipedEndpoint: "https://fake-server-url-value",
pinnipedCABundle: "fake-certificate-authority-data-value",
namespace: "test-namespace",
authenticatorType: "test-authenticator-type",
authenticatorName: "test-authenticator-name",
}.String(),
},
{
name: "success using local CA data and discovered authenticator",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.authenticatorName = ""
cmd.flags.authenticatorType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
&authv1alpha.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "discovered-authenticator"}},
newCredentialIssuer("pinniped-config", "test-namespace", "https://example.com", "test-ca"),
), nil
}
},
wantStderr: `WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuer on the cluster. Using local kubeconfig values.`,
wantStdout: expectedKubeconfigYAML{
clusterCAData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
clusterServer: "https://fake-server-url-value",
command: "/path/to/pinniped",
token: "test-token",
pinnipedEndpoint: "https://fake-server-url-value",
pinnipedCABundle: "fake-certificate-authority-data-value",
namespace: "test-namespace",
authenticatorType: "webhook",
authenticatorName: "discovered-authenticator",
}.String(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Start with a default getKubeConfigCommand, set some defaults, then apply any mocks.
c := newGetKubeConfigCommand()
c.flags.token = "test-token"
c.flags.namespace = "test-namespace"
c.flags.authenticatorName = "test-authenticator-name"
c.flags.authenticatorType = "test-authenticator-type"
c.getPathToSelf = func() (string, error) { return "/path/to/pinniped", nil }
c.flags.kubeconfig = "./testdata/kubeconfig.yaml"
tt.mocks(c)
cmd := &cobra.Command{}
var stdout, stderr bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
cmd.SetArgs([]string{})
err := c.run(cmd, []string{})
if tt.wantError != "" {
require.EqualError(t, err, tt.wantError)
} else {
require.NoError(t, err)
}
require.Equal(t, strings.TrimSpace(tt.wantStdout), strings.TrimSpace(stdout.String()), "unexpected stdout")
require.Equal(t, strings.TrimSpace(tt.wantStderr), strings.TrimSpace(stderr.String()), "unexpected stderr")
})
}
}

View File

@@ -0,0 +1,384 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
conciergev1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
conciergeclientset "go.pinniped.dev/generated/1.20/client/concierge/clientset/versioned"
"go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/kubeclient"
)
type kubeconfigDeps struct {
getPathToSelf func() (string, error)
getClientset func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error)
}
func kubeconfigRealDeps() kubeconfigDeps {
return kubeconfigDeps{
getPathToSelf: os.Executable,
getClientset: func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
client, err := kubeclient.New(
kubeclient.WithConfig(restConfig),
kubeclient.WithMiddleware(groupsuffix.New(apiGroupSuffix)),
)
if err != nil {
return nil, err
}
return client.PinnipedConcierge, nil
},
}
}
//nolint: gochecknoinits
func init() {
getCmd.AddCommand(kubeconfigCommand(kubeconfigRealDeps()))
}
type getKubeconfigOIDCParams struct {
issuer string
clientID string
listenPort uint16
scopes []string
skipBrowser bool
sessionCachePath string
debugSessionCache bool
caBundlePaths []string
requestAudience string
}
type getKubeconfigConciergeParams struct {
disabled bool
authenticatorName string
authenticatorType string
apiGroupSuffix string
}
type getKubeconfigParams struct {
kubeconfigPath string
kubeconfigContextOverride string
staticToken string
staticTokenEnvName string
oidc getKubeconfigOIDCParams
concierge getKubeconfigConciergeParams
}
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
var (
cmd = &cobra.Command{
Args: cobra.NoArgs,
Use: "kubeconfig",
Short: "Generate a Pinniped-based kubeconfig for a cluster",
SilenceUsage: true,
}
flags getKubeconfigParams
namespace string // unused now
)
f := cmd.Flags()
f.StringVar(&flags.staticToken, "static-token", "", "Instead of doing an OIDC-based login, specify a static token")
f.StringVar(&flags.staticTokenEnvName, "static-token-env", "", "Instead of doing an OIDC-based login, read a static token from the environment")
f.BoolVar(&flags.concierge.disabled, "no-concierge", false, "Generate a configuration which does not use the concierge, but sends the credential to the cluster directly")
f.StringVar(&namespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
f.StringVar(&flags.concierge.authenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)")
f.StringVar(&flags.concierge.authenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name (default: autodiscover)")
f.StringVar(&flags.concierge.apiGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
f.StringVar(&flags.oidc.issuer, "oidc-issuer", "", "OpenID Connect issuer URL (default: autodiscover)")
f.StringVar(&flags.oidc.clientID, "oidc-client-id", "pinniped-cli", "OpenID Connect client ID (default: autodiscover)")
f.Uint16Var(&flags.oidc.listenPort, "oidc-listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
f.StringSliceVar(&flags.oidc.scopes, "oidc-scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OpenID Connect scopes to request during login")
f.BoolVar(&flags.oidc.skipBrowser, "oidc-skip-browser", false, "During OpenID Connect login, skip opening the browser (just print the URL)")
f.StringVar(&flags.oidc.sessionCachePath, "oidc-session-cache", "", "Path to OpenID Connect session cache file")
f.StringSliceVar(&flags.oidc.caBundlePaths, "oidc-ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
f.BoolVar(&flags.oidc.debugSessionCache, "oidc-debug-session-cache", false, "Print debug logs related to the OpenID Connect session cache")
f.StringVar(&flags.oidc.requestAudience, "oidc-request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
mustMarkHidden(cmd, "oidc-debug-session-cache")
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
mustMarkHidden(cmd, "concierge-namespace")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runGetKubeconfig(cmd.OutOrStdout(), deps, flags) }
return cmd
}
//nolint:funlen
func runGetKubeconfig(out io.Writer, deps kubeconfigDeps, flags getKubeconfigParams) error {
// Validate api group suffix and immediately return an error if it is invalid.
if err := groupsuffix.Validate(flags.concierge.apiGroupSuffix); err != nil {
return fmt.Errorf("invalid api group suffix: %w", err)
}
execConfig := clientcmdapi.ExecConfig{
APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(),
Args: []string{},
Env: []clientcmdapi.ExecEnvVar{},
}
var err error
execConfig.Command, err = deps.getPathToSelf()
if err != nil {
return fmt.Errorf("could not determine the Pinniped executable path: %w", err)
}
execConfig.ProvideClusterInfo = true
oidcCABundle, err := loadCABundlePaths(flags.oidc.caBundlePaths)
if err != nil {
return fmt.Errorf("could not read --oidc-ca-bundle: %w", err)
}
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
currentKubeConfig, err := clientConfig.RawConfig()
if err != nil {
return fmt.Errorf("could not load --kubeconfig: %w", err)
}
cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, flags.kubeconfigContextOverride)
if err != nil {
return fmt.Errorf("could not load --kubeconfig/--kubeconfig-context: %w", err)
}
clientset, err := deps.getClientset(clientConfig, flags.concierge.apiGroupSuffix)
if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err)
}
if !flags.concierge.disabled {
authenticator, err := lookupAuthenticator(
clientset,
flags.concierge.authenticatorType,
flags.concierge.authenticatorName,
)
if err != nil {
return err
}
if err := configureConcierge(authenticator, &flags, cluster, &oidcCABundle, &execConfig); err != nil {
return err
}
}
// If one of the --static-* flags was passed, output a config that runs `pinniped login static`.
if flags.staticToken != "" || flags.staticTokenEnvName != "" {
if flags.staticToken != "" && flags.staticTokenEnvName != "" {
return fmt.Errorf("only one of --static-token and --static-token-env can be specified")
}
execConfig.Args = append([]string{"login", "static"}, execConfig.Args...)
if flags.staticToken != "" {
execConfig.Args = append(execConfig.Args, "--token="+flags.staticToken)
}
if flags.staticTokenEnvName != "" {
execConfig.Args = append(execConfig.Args, "--token-env="+flags.staticTokenEnvName)
}
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
}
// Otherwise continue to parse the OIDC-related flags and output a config that runs `pinniped login oidc`.
execConfig.Args = append([]string{"login", "oidc"}, execConfig.Args...)
if flags.oidc.issuer == "" {
return fmt.Errorf("could not autodiscover --oidc-issuer, and none was provided")
}
execConfig.Args = append(execConfig.Args,
"--issuer="+flags.oidc.issuer,
"--client-id="+flags.oidc.clientID,
"--scopes="+strings.Join(flags.oidc.scopes, ","),
)
if flags.oidc.skipBrowser {
execConfig.Args = append(execConfig.Args, "--skip-browser")
}
if flags.oidc.listenPort != 0 {
execConfig.Args = append(execConfig.Args, "--listen-port="+strconv.Itoa(int(flags.oidc.listenPort)))
}
if oidcCABundle != "" {
execConfig.Args = append(execConfig.Args, "--ca-bundle-data="+base64.StdEncoding.EncodeToString([]byte(oidcCABundle)))
}
if flags.oidc.sessionCachePath != "" {
execConfig.Args = append(execConfig.Args, "--session-cache="+flags.oidc.sessionCachePath)
}
if flags.oidc.debugSessionCache {
execConfig.Args = append(execConfig.Args, "--debug-session-cache")
}
if flags.oidc.requestAudience != "" {
execConfig.Args = append(execConfig.Args, "--request-audience="+flags.oidc.requestAudience)
}
return writeConfigAsYAML(out, newExecKubeconfig(cluster, &execConfig))
}
func configureConcierge(authenticator metav1.Object, flags *getKubeconfigParams, v1Cluster *clientcmdapi.Cluster, oidcCABundle *string, execConfig *clientcmdapi.ExecConfig) error {
switch auth := authenticator.(type) {
case *conciergev1alpha1.WebhookAuthenticator:
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
// them to point at the discovered WebhookAuthenticator.
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
flags.concierge.authenticatorType = "webhook"
flags.concierge.authenticatorName = auth.Name
}
case *conciergev1alpha1.JWTAuthenticator:
// If the --concierge-authenticator-type/--concierge-authenticator-name flags were not set explicitly, set
// them to point at the discovered JWTAuthenticator.
if flags.concierge.authenticatorType == "" && flags.concierge.authenticatorName == "" {
flags.concierge.authenticatorType = "jwt"
flags.concierge.authenticatorName = auth.Name
}
// If the --oidc-issuer flag was not set explicitly, default it to the spec.issuer field of the JWTAuthenticator.
if flags.oidc.issuer == "" {
flags.oidc.issuer = auth.Spec.Issuer
}
// If the --oidc-request-audience flag was not set explicitly, default it to the spec.audience field of the JWTAuthenticator.
if flags.oidc.requestAudience == "" {
flags.oidc.requestAudience = auth.Spec.Audience
}
// If the --oidc-ca-bundle flags was not set explicitly, default it to the
// spec.tls.certificateAuthorityData field of the JWTAuthenticator.
if *oidcCABundle == "" && auth.Spec.TLS != nil && auth.Spec.TLS.CertificateAuthorityData != "" {
decoded, err := base64.StdEncoding.DecodeString(auth.Spec.TLS.CertificateAuthorityData)
if err != nil {
return fmt.Errorf("tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator %s has invalid spec.tls.certificateAuthorityData: %w", auth.Name, err)
}
*oidcCABundle = string(decoded)
}
}
// Append the flags to configure the Concierge credential exchange at runtime.
execConfig.Args = append(execConfig.Args,
"--enable-concierge",
"--concierge-api-group-suffix="+flags.concierge.apiGroupSuffix,
"--concierge-authenticator-name="+flags.concierge.authenticatorName,
"--concierge-authenticator-type="+flags.concierge.authenticatorType,
"--concierge-endpoint="+v1Cluster.Server,
"--concierge-ca-bundle-data="+base64.StdEncoding.EncodeToString(v1Cluster.CertificateAuthorityData),
)
return nil
}
func loadCABundlePaths(paths []string) (string, error) {
if len(paths) == 0 {
return "", nil
}
blobs := make([][]byte, 0, len(paths))
for _, p := range paths {
pem, err := ioutil.ReadFile(p)
if err != nil {
return "", err
}
blobs = append(blobs, pem)
}
return string(bytes.Join(blobs, []byte("\n"))), nil
}
func newExecKubeconfig(cluster *clientcmdapi.Cluster, execConfig *clientcmdapi.ExecConfig) clientcmdapi.Config {
const name = "pinniped"
return clientcmdapi.Config{
Kind: "Config",
APIVersion: clientcmdapi.SchemeGroupVersion.Version,
Clusters: map[string]*clientcmdapi.Cluster{name: cluster},
AuthInfos: map[string]*clientcmdapi.AuthInfo{name: {Exec: execConfig}},
Contexts: map[string]*clientcmdapi.Context{name: {Cluster: name, AuthInfo: name}},
CurrentContext: name,
}
}
func lookupAuthenticator(clientset conciergeclientset.Interface, authType, authName string) (metav1.Object, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
// If one was specified, look it up or error.
if authName != "" && authType != "" {
switch strings.ToLower(authType) {
case "webhook":
return clientset.AuthenticationV1alpha1().WebhookAuthenticators().Get(ctx, authName, metav1.GetOptions{})
case "jwt":
return clientset.AuthenticationV1alpha1().JWTAuthenticators().Get(ctx, authName, metav1.GetOptions{})
default:
return nil, fmt.Errorf(`invalid authenticator type %q, supported values are "webhook" and "jwt"`, authType)
}
}
// Otherwise list all the available authenticators and hope there's just a single one.
jwtAuths, err := clientset.AuthenticationV1alpha1().JWTAuthenticators().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list JWTAuthenticator objects for autodiscovery: %w", err)
}
webhooks, err := clientset.AuthenticationV1alpha1().WebhookAuthenticators().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to list WebhookAuthenticator objects for autodiscovery: %w", err)
}
results := make([]metav1.Object, 0, len(jwtAuths.Items)+len(webhooks.Items))
for i := range jwtAuths.Items {
results = append(results, &jwtAuths.Items[i])
}
for i := range webhooks.Items {
results = append(results, &webhooks.Items[i])
}
if len(results) == 0 {
return nil, fmt.Errorf("no authenticators were found")
}
if len(results) > 1 {
return nil, fmt.Errorf("multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified")
}
return results[0], nil
}
func newClientConfig(kubeconfigPathOverride string, currentContextName string) clientcmd.ClientConfig {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.ExplicitPath = kubeconfigPathOverride
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{
CurrentContext: currentContextName,
})
return clientConfig
}
func writeConfigAsYAML(out io.Writer, config clientcmdapi.Config) error {
output, err := clientcmd.Write(config)
if err != nil {
return err
}
_, err = out.Write(output)
if err != nil {
return fmt.Errorf("could not write output: %w", err)
}
return nil
}
func copyCurrentClusterFromExistingKubeConfig(currentKubeConfig clientcmdapi.Config, currentContextNameOverride string) (*clientcmdapi.Cluster, error) {
contextName := currentKubeConfig.CurrentContext
if currentContextNameOverride != "" {
contextName = currentContextNameOverride
}
context := currentKubeConfig.Contexts[contextName]
if context == nil {
return nil, fmt.Errorf("no such context %q", contextName)
}
return currentKubeConfig.Clusters[context.Cluster], nil
}

View File

@@ -0,0 +1,540 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"crypto/x509/pkix"
"encoding/base64"
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubetesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
conciergev1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
conciergeclientset "go.pinniped.dev/generated/1.20/client/concierge/clientset/versioned"
fakeconciergeclientset "go.pinniped.dev/generated/1.20/client/concierge/clientset/versioned/fake"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
)
func TestGetKubeconfig(t *testing.T) {
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
require.NoError(t, err)
tmpdir := testutil.TempDir(t)
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, ioutil.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
tests := []struct {
name string
args []string
env map[string]string
getPathToSelfErr error
getClientsetErr error
conciergeObjects []runtime.Object
conciergeReactions []kubetesting.Reactor
wantError bool
wantStdout string
wantStderr string
wantOptionsCount int
wantAPIGroupSuffix string
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: here.Doc(`
Generate a Pinniped-based kubeconfig for a cluster
Usage:
kubeconfig [flags]
Flags:
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
-h, --help help for kubeconfig
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
--no-concierge Generate a configuration which does not use the concierge, but sends the credential to the cluster directly
--oidc-ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--oidc-session-cache string Path to OpenID Connect session cache file
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
`),
},
{
name: "fail to get self-path",
args: []string{},
getPathToSelfErr: fmt.Errorf("some OS error"),
wantError: true,
wantStderr: here.Doc(`
Error: could not determine the Pinniped executable path: some OS error
`),
},
{
name: "invalid CA bundle paths",
args: []string{
"--oidc-ca-bundle", "./does/not/exist",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not read --oidc-ca-bundle: open ./does/not/exist: no such file or directory
`),
},
{
name: "invalid kubeconfig path",
args: []string{
"--kubeconfig", "./does/not/exist",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory
`),
},
{
name: "invalid kubeconfig context",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--kubeconfig-context", "invalid",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"
`),
},
{
name: "clientset creation failure",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
getClientsetErr: fmt.Errorf("some kube error"),
wantError: true,
wantStderr: here.Doc(`
Error: could not configure Kubernetes client: some kube error
`),
},
{
name: "webhook authenticator not found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
},
wantError: true,
wantStderr: here.Doc(`
Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
`),
},
{
name: "JWT authenticator not found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator",
},
wantError: true,
wantStderr: here.Doc(`
Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
`),
},
{
name: "invalid authenticator type",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-authenticator-type", "invalid",
"--concierge-authenticator-name", "test-authenticator",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"
`),
},
{
name: "fail to autodetect authenticator, listing jwtauthenticators fails",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{
Verb: "*",
Resource: "jwtauthenticators",
Reaction: func(kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some list error")
},
},
},
wantError: true,
wantStderr: here.Doc(`
Error: failed to list JWTAuthenticator objects for autodiscovery: some list error
`),
},
{
name: "fail to autodetect authenticator, listing webhookauthenticators fails",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeReactions: []kubetesting.Reactor{
&kubetesting.SimpleReactor{
Verb: "*",
Resource: "webhookauthenticators",
Reaction: func(kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some list error")
},
},
},
wantError: true,
wantStderr: here.Doc(`
Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error
`),
},
{
name: "fail to autodetect authenticator, none found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
wantError: true,
wantStderr: here.Doc(`
Error: no authenticators were found
`),
},
{
name: "fail to autodetect authenticator, multiple found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-1"}},
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-2"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-3"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified
`),
},
{
name: "autodetect webhook authenticator, missing --oidc-issuer",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: could not autodiscover --oidc-issuer, and none was provided
`),
},
{
name: "autodetect JWT authenticator, invalid TLS bundle",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: "invalid-base64",
},
},
},
},
wantError: true,
wantStderr: here.Doc(`
Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7
`),
},
{
name: "invalid static token flags",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--static-token", "test-token",
"--static-token-env", "TEST_TOKEN",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: only one of --static-token and --static-token-env can be specified
`),
},
{
name: "invalid api group suffix",
args: []string{
"--concierge-api-group-suffix", ".starts.with.dot",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid api group suffix: a lowercase RFC 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])?)*')
`),
},
{
name: "valid static token",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--static-token", "test-token",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantStdout: here.Doc(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
kind: Config
preferences: {}
users:
- name: pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- static
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --token=test-token
command: '.../path/to/pinniped'
env: []
provideClusterInfo: true
`),
},
{
name: "valid static token from env var",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--static-token-env", "TEST_TOKEN",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"}},
},
wantStdout: here.Doc(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
kind: Config
preferences: {}
users:
- name: pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- static
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --token-env=TEST_TOKEN
command: '.../path/to/pinniped'
env: []
provideClusterInfo: true
`),
},
{
name: "autodetect JWT authenticator",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
Issuer: "https://example.com/issuer",
Audience: "test-audience",
TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: base64.StdEncoding.EncodeToString(testCA.Bundle()),
},
},
},
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
kind: Config
preferences: {}
users:
- name: pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience
- --ca-bundle-data=%s
- --request-audience=test-audience
command: '.../path/to/pinniped'
env: []
provideClusterInfo: true
`, base64.StdEncoding.EncodeToString(testCA.Bundle())),
},
{
name: "autodetect nothing, set a bunch of options",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-api-group-suffix", "tuna.io",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
"--oidc-issuer", "https://example.com/issuer",
"--oidc-skip-browser",
"--oidc-listen-port", "1234",
"--oidc-ca-bundle", testCABundlePath,
"--oidc-session-cache", "/path/to/cache/dir/sessions.yaml",
"--oidc-debug-session-cache",
"--oidc-request-audience", "test-audience",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator"},
},
},
wantStdout: here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: pinniped
contexts:
- context:
cluster: pinniped
user: pinniped
name: pinniped
current-context: pinniped
kind: Config
preferences: {}
users:
- name: pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=tuna.io
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=webhook
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=https://example.com/issuer
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience
- --skip-browser
- --listen-port=1234
- --ca-bundle-data=%s
- --session-cache=/path/to/cache/dir/sessions.yaml
- --debug-session-cache
- --request-audience=test-audience
command: '.../path/to/pinniped'
env: []
provideClusterInfo: true
`, base64.StdEncoding.EncodeToString(testCA.Bundle())),
wantAPIGroupSuffix: "tuna.io",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
cmd := kubeconfigCommand(kubeconfigDeps{
getPathToSelf: func() (string, error) {
if tt.getPathToSelfErr != nil {
return "", tt.getPathToSelfErr
}
return ".../path/to/pinniped", nil
},
getClientset: func(clientConfig clientcmd.ClientConfig, apiGroupSuffix string) (conciergeclientset.Interface, error) {
if tt.wantAPIGroupSuffix == "" {
require.Equal(t, "pinniped.dev", apiGroupSuffix) // "pinniped.dev" = api group suffix default
} else {
require.Equal(t, tt.wantAPIGroupSuffix, apiGroupSuffix)
}
if tt.getClientsetErr != nil {
return nil, tt.getClientsetErr
}
fake := fakeconciergeclientset.NewSimpleClientset(tt.conciergeObjects...)
if len(tt.conciergeReactions) > 0 {
fake.ReactionChain = tt.conciergeReactions
}
return fake, nil
},
})
require.NotNil(t, cmd)
var stdout, stderr bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if tt.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
})
}
}

View File

@@ -12,7 +12,8 @@ var loginCmd = &cobra.Command{
Use: "login",
Short: "login",
Long: "Login to a Pinniped server",
SilenceUsage: true, // do not print usage message when commands fail
SilenceUsage: true, // Do not print usage message when commands fail.
Hidden: true, // These commands are not really meant to be used directly by users, so it's confusing to have them discoverable.
}
//nolint: gochecknoinits

View File

@@ -1,103 +1,228 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/klog/v2/klogr"
"go.pinniped.dev/internal/oidcclient"
"go.pinniped.dev/internal/oidcclient/filesession"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/filesession"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
//nolint: gochecknoinits
func init() {
loginCmd.AddCommand(oidcLoginCommand(oidcclient.Login))
loginCmd.AddCommand(oidcLoginCommand(oidcLoginCommandRealDeps()))
}
func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error)) *cobra.Command {
type oidcLoginCommandDeps struct {
login func(string, string, ...oidcclient.Option) (*oidctypes.Token, error)
exchangeToken func(context.Context, *conciergeclient.Client, string) (*clientauthv1beta1.ExecCredential, error)
}
func oidcLoginCommandRealDeps() oidcLoginCommandDeps {
return oidcLoginCommandDeps{
login: oidcclient.Login,
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
return client.ExchangeToken(ctx, token)
},
}
}
type oidcLoginFlags struct {
issuer string
clientID string
listenPort uint16
scopes []string
skipBrowser bool
sessionCachePath string
caBundlePaths []string
caBundleData []string
debugSessionCache bool
requestAudience string
conciergeEnabled bool
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
}
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
var (
cmd = cobra.Command{
cmd = &cobra.Command{
Args: cobra.NoArgs,
Use: "oidc --issuer ISSUER --client-id CLIENT_ID",
Use: "oidc --issuer ISSUER",
Short: "Login using an OpenID Connect provider",
SilenceUsage: true,
}
issuer string
clientID string
listenPort uint16
scopes []string
skipBrowser bool
sessionCachePath string
debugSessionCache bool
flags oidcLoginFlags
conciergeNamespace string // unused now
)
cmd.Flags().StringVar(&issuer, "issuer", "", "OpenID Connect issuer URL.")
cmd.Flags().StringVar(&clientID, "client-id", "", "OpenID Connect client ID.")
cmd.Flags().Uint16Var(&listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only).")
cmd.Flags().StringSliceVar(&scopes, "scopes", []string{"offline_access", "openid", "email", "profile"}, "OIDC scopes to request during login.")
cmd.Flags().BoolVar(&skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL).")
cmd.Flags().StringVar(&sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file.")
cmd.Flags().BoolVar(&debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache.")
mustMarkHidden(&cmd, "debug-session-cache")
mustMarkRequired(&cmd, "issuer", "client-id")
cmd.Flags().StringVar(&flags.issuer, "issuer", "", "OpenID Connect issuer URL")
cmd.Flags().StringVar(&flags.clientID, "client-id", "pinniped-cli", "OpenID Connect client ID")
cmd.Flags().Uint16Var(&flags.listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only)")
cmd.Flags().StringSliceVar(&flags.scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped:request-audience"}, "OIDC scopes to request during login")
cmd.Flags().BoolVar(&flags.skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL)")
cmd.Flags().StringVar(&flags.sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file")
cmd.Flags().StringSliceVar(&flags.caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated)")
cmd.Flags().StringSliceVar(&flags.caBundleData, "ca-bundle-data", nil, "Base64 endcoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)")
cmd.Flags().BoolVar(&flags.debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache")
cmd.Flags().StringVar(&flags.requestAudience, "request-audience", "", "Request a token with an alternate audience using RFC8693 token exchange")
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Exchange the OIDC ID token with the Pinniped concierge during login")
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
cmd.Flags().StringVar(&flags.conciergeAuthenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt')")
cmd.Flags().StringVar(&flags.conciergeAuthenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name")
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped concierge endpoint")
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the concierge")
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
cmd.RunE = func(cmd *cobra.Command, args []string) error {
// Initialize the session cache.
var sessionOptions []filesession.Option
mustMarkHidden(cmd, "debug-session-cache")
mustMarkRequired(cmd, "issuer")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runOIDCLogin(cmd, deps, flags) }
// If the hidden --debug-session-cache option is passed, log all the errors from the session cache with klog.
if debugSessionCache {
logger := klogr.New().WithName("session")
sessionOptions = append(sessionOptions, filesession.WithErrorReporter(func(err error) {
logger.Error(err, "error during session cache operation")
}))
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
mustMarkHidden(cmd, "concierge-namespace")
return cmd
}
func runOIDCLogin(cmd *cobra.Command, deps oidcLoginCommandDeps, flags oidcLoginFlags) error {
// Initialize the session cache.
var sessionOptions []filesession.Option
// If the hidden --debug-session-cache option is passed, log all the errors from the session cache with klog.
if flags.debugSessionCache {
logger := klogr.New().WithName("session")
sessionOptions = append(sessionOptions, filesession.WithErrorReporter(func(err error) {
logger.Error(err, "error during session cache operation")
}))
}
sessionCache := filesession.New(flags.sessionCachePath, sessionOptions...)
// Initialize the login handler.
opts := []oidcclient.Option{
oidcclient.WithContext(cmd.Context()),
oidcclient.WithScopes(flags.scopes),
oidcclient.WithSessionCache(sessionCache),
}
if flags.listenPort != 0 {
opts = append(opts, oidcclient.WithListenPort(flags.listenPort))
}
if flags.requestAudience != "" {
opts = append(opts, oidcclient.WithRequestAudience(flags.requestAudience))
}
var concierge *conciergeclient.Client
if flags.conciergeEnabled {
var err error
concierge, err = conciergeclient.New(
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
)
if err != nil {
return fmt.Errorf("invalid concierge parameters: %w", err)
}
sessionCache := filesession.New(sessionCachePath, sessionOptions...)
}
// Initialize the login handler.
opts := []oidcclient.Option{
oidcclient.WithContext(cmd.Context()),
oidcclient.WithScopes(scopes),
oidcclient.WithSessionCache(sessionCache),
}
// --skip-browser replaces the default "browser open" function with one that prints to stderr.
if flags.skipBrowser {
opts = append(opts, oidcclient.WithBrowserOpen(func(url string) error {
cmd.PrintErr("Please log in: ", url, "\n")
return nil
}))
}
if listenPort != 0 {
opts = append(opts, oidcclient.WithListenPort(listenPort))
}
// --skip-browser replaces the default "browser open" function with one that prints to stderr.
if skipBrowser {
opts = append(opts, oidcclient.WithBrowserOpen(func(url string) error {
cmd.PrintErr("Please log in: ", url, "\n")
return nil
}))
}
tok, err := loginFunc(issuer, clientID, opts...)
if len(flags.caBundlePaths) > 0 || len(flags.caBundleData) > 0 {
client, err := makeClient(flags.caBundlePaths, flags.caBundleData)
if err != nil {
return err
}
// Convert the token out to Kubernetes ExecCredential JSON format for output.
return json.NewEncoder(cmd.OutOrStdout()).Encode(&clientauthenticationv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthenticationv1beta1.ExecCredentialStatus{
ExpirationTimestamp: &tok.IDToken.Expiry,
Token: tok.IDToken.Token,
},
})
opts = append(opts, oidcclient.WithClient(client))
}
return &cmd
// Do the basic login to get an OIDC token.
token, err := deps.login(flags.issuer, flags.clientID, opts...)
if err != nil {
return fmt.Errorf("could not complete Pinniped login: %w", err)
}
cred := tokenCredential(token)
// If the concierge was configured, exchange the credential for a separate short-lived, cluster-specific credential.
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if concierge != nil {
cred, err = deps.exchangeToken(ctx, concierge, token.IDToken.Token)
if err != nil {
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
}
}
return json.NewEncoder(cmd.OutOrStdout()).Encode(cred)
}
func makeClient(caBundlePaths []string, caBundleData []string) (*http.Client, error) {
pool := x509.NewCertPool()
for _, p := range caBundlePaths {
pem, err := ioutil.ReadFile(p)
if err != nil {
return nil, fmt.Errorf("could not read --ca-bundle: %w", err)
}
pool.AppendCertsFromPEM(pem)
}
for _, d := range caBundleData {
pem, err := base64.StdEncoding.DecodeString(d)
if err != nil {
return nil, fmt.Errorf("could not read --ca-bundle-data: %w", err)
}
pool.AppendCertsFromPEM(pem)
}
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
RootCAs: pool,
MinVersion: tls.VersionTLS12,
},
},
}, nil
}
func tokenCredential(token *oidctypes.Token) *clientauthv1beta1.ExecCredential {
cred := clientauthv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthv1beta1.ExecCredentialStatus{
Token: token.IDToken.Token,
},
}
if !token.IDToken.Expiry.IsZero() {
cred.Status.ExpirationTimestamp = &token.IDToken.Expiry
}
return &cred
}
// mustGetConfigDir returns a directory that follows the XDG base directory convention:

View File

@@ -1,35 +1,50 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"context"
"crypto/x509/pkix"
"encoding/base64"
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/oidcclient"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
func TestLoginOIDCCommand(t *testing.T) {
t.Parallel()
cfgDir := mustGetConfigDir()
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
require.NoError(t, err)
tmpdir := testutil.TempDir(t)
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, ioutil.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
time1 := time.Date(3020, 10, 12, 13, 14, 15, 16, time.UTC)
tests := []struct {
name string
args []string
loginErr error
conciergeErr error
wantError bool
wantStdout string
wantStderr string
wantIssuer string
wantClientID string
wantOptionsCount int
}{
{
@@ -39,24 +54,114 @@ func TestLoginOIDCCommand(t *testing.T) {
Login using an OpenID Connect provider
Usage:
oidc --issuer ISSUER --client-id CLIENT_ID [flags]
oidc --issuer ISSUER [flags]
Flags:
--client-id string OpenID Connect client ID.
-h, --help help for oidc
--issuer string OpenID Connect issuer URL.
--listen-port uint16 TCP port for localhost listener (authorization code flow only).
--scopes strings OIDC scopes to request during login. (default [offline_access,openid,email,profile])
--session-cache string Path to session cache file. (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL).
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--ca-bundle-data strings Base64 endcoded TLS certificate authority bundle (base64 encoded PEM format, optional, can be repeated)
--client-id string OpenID Connect client ID (default "pinniped-cli")
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the concierge
--concierge-endpoint string API base for the Pinniped concierge endpoint
--enable-concierge Exchange the OIDC ID token with the Pinniped concierge during login
-h, --help help for oidc
--issuer string OpenID Connect issuer URL
--listen-port uint16 TCP port for localhost listener (authorization code flow only)
--request-audience string Request a token with an alternate audience using RFC8693 token exchange
--scopes strings OIDC scopes to request during login (default [offline_access,openid,pinniped:request-audience])
--session-cache string Path to session cache file (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL)
`),
},
{
name: "missing required flags",
args: []string{},
wantError: true,
wantStdout: here.Doc(`
Error: required flag(s) "client-id", "issuer" not set
wantStderr: here.Doc(`
Error: required flag(s) "issuer" not set
`),
},
{
name: "missing concierge flags",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--enable-concierge",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid concierge parameters: endpoint must not be empty
`),
},
{
name: "invalid CA bundle path",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--ca-bundle", "./does/not/exist",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not read --ca-bundle: open ./does/not/exist: no such file or directory
`),
},
{
name: "invalid CA bundle data",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--ca-bundle-data", "invalid-base64",
},
wantError: true,
wantStderr: here.Doc(`
Error: could not read --ca-bundle-data: illegal base64 data at input byte 7
`),
},
{
name: "invalid api group suffix",
args: []string{
"--issuer", "test-issuer",
"--enable-concierge",
"--concierge-api-group-suffix", ".starts.with.dot",
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://127.0.0.1:1234/",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid concierge parameters: invalid api group suffix: a lowercase RFC 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])?)*')
`),
},
{
name: "login error",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
},
loginErr: fmt.Errorf("some login error"),
wantOptionsCount: 3,
wantError: true,
wantStderr: here.Doc(`
Error: could not complete Pinniped login: some login error
`),
},
{
name: "concierge token exchange error",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--enable-concierge",
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://127.0.0.1:1234/",
},
conciergeErr: fmt.Errorf("some concierge error"),
wantOptionsCount: 3,
wantError: true,
wantStderr: here.Doc(`
Error: could not complete concierge credential exchange: some concierge error
`),
},
{
@@ -65,8 +170,6 @@ func TestLoginOIDCCommand(t *testing.T) {
"--client-id", "test-client-id",
"--issuer", "test-issuer",
},
wantIssuer: "test-issuer",
wantClientID: "test-client-id",
wantOptionsCount: 3,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
},
@@ -78,32 +181,56 @@ func TestLoginOIDCCommand(t *testing.T) {
"--skip-browser",
"--listen-port", "1234",
"--debug-session-cache",
"--request-audience", "cluster-1234",
"--ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
"--ca-bundle", testCABundlePath,
"--enable-concierge",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://127.0.0.1:1234/",
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
"--concierge-api-group-suffix", "some.suffix.com",
},
wantIssuer: "test-issuer",
wantClientID: "test-client-id",
wantOptionsCount: 5,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
wantOptionsCount: 7,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"exchanged-token"}}` + "\n",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var (
gotIssuer string
gotClientID string
gotOptions []oidcclient.Option
gotOptions []oidcclient.Option
)
cmd := oidcLoginCommand(func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error) {
gotIssuer = issuer
gotClientID = clientID
gotOptions = opts
return &oidcclient.Token{
IDToken: &oidcclient.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time1),
},
}, nil
cmd := oidcLoginCommand(oidcLoginCommandDeps{
login: func(issuer string, clientID string, opts ...oidcclient.Option) (*oidctypes.Token, error) {
require.Equal(t, "test-issuer", issuer)
require.Equal(t, "test-client-id", clientID)
gotOptions = opts
if tt.loginErr != nil {
return nil, tt.loginErr
}
return &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time1),
},
}, nil
},
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
require.Equal(t, token, "test-id-token")
if tt.conciergeErr != nil {
return nil, tt.conciergeErr
}
return &clientauthv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthv1beta1.ExecCredentialStatus{
Token: "exchanged-token",
},
}, nil
},
})
require.NotNil(t, cmd)
@@ -119,8 +246,6 @@ func TestLoginOIDCCommand(t *testing.T) {
}
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
require.Equal(t, tt.wantIssuer, gotIssuer, "unexpected issuer")
require.Equal(t, tt.wantClientID, gotClientID, "unexpected client ID")
require.Len(t, gotOptions, tt.wantOptionsCount)
})
}

View File

@@ -0,0 +1,126 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"time"
"github.com/spf13/cobra"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
//nolint: gochecknoinits
func init() {
loginCmd.AddCommand(staticLoginCommand(staticLoginRealDeps()))
}
type staticLoginDeps struct {
lookupEnv func(string) (string, bool)
exchangeToken func(context.Context, *conciergeclient.Client, string) (*clientauthv1beta1.ExecCredential, error)
}
func staticLoginRealDeps() staticLoginDeps {
return staticLoginDeps{
lookupEnv: os.LookupEnv,
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
return client.ExchangeToken(ctx, token)
},
}
}
type staticLoginParams struct {
staticToken string
staticTokenEnvName string
conciergeEnabled bool
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle string
conciergeAPIGroupSuffix string
}
func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
var (
cmd = &cobra.Command{
Args: cobra.NoArgs,
Use: "static [--token TOKEN] [--token-env TOKEN_NAME]",
Short: "Login using a static token",
SilenceUsage: true,
}
flags staticLoginParams
conciergeNamespace string // unused now
)
cmd.Flags().StringVar(&flags.staticToken, "token", "", "Static token to present during login")
cmd.Flags().StringVar(&flags.staticTokenEnvName, "token-env", "", "Environment variable containing a static token")
cmd.Flags().BoolVar(&flags.conciergeEnabled, "enable-concierge", false, "Exchange the token with the Pinniped concierge during login")
cmd.Flags().StringVar(&conciergeNamespace, "concierge-namespace", "pinniped-concierge", "Namespace in which the concierge was installed")
cmd.Flags().StringVar(&flags.conciergeAuthenticatorType, "concierge-authenticator-type", "", "Concierge authenticator type (e.g., 'webhook', 'jwt')")
cmd.Flags().StringVar(&flags.conciergeAuthenticatorName, "concierge-authenticator-name", "", "Concierge authenticator name")
cmd.Flags().StringVar(&flags.conciergeEndpoint, "concierge-endpoint", "", "API base for the Pinniped concierge endpoint")
cmd.Flags().StringVar(&flags.conciergeCABundle, "concierge-ca-bundle-data", "", "CA bundle to use when connecting to the concierge")
cmd.Flags().StringVar(&flags.conciergeAPIGroupSuffix, "concierge-api-group-suffix", "pinniped.dev", "Concierge API group suffix")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd.OutOrStdout(), deps, flags) }
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
mustMarkHidden(cmd, "concierge-namespace")
return cmd
}
func runStaticLogin(out io.Writer, deps staticLoginDeps, flags staticLoginParams) error {
if flags.staticToken == "" && flags.staticTokenEnvName == "" {
return fmt.Errorf("one of --token or --token-env must be set")
}
var concierge *conciergeclient.Client
if flags.conciergeEnabled {
var err error
concierge, err = conciergeclient.New(
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
conciergeclient.WithAPIGroupSuffix(flags.conciergeAPIGroupSuffix),
)
if err != nil {
return fmt.Errorf("invalid concierge parameters: %w", err)
}
}
var token string
if flags.staticToken != "" {
token = flags.staticToken
}
if flags.staticTokenEnvName != "" {
var ok bool
token, ok = deps.lookupEnv(flags.staticTokenEnvName)
if !ok {
return fmt.Errorf("--token-env variable %q is not set", flags.staticTokenEnvName)
}
if token == "" {
return fmt.Errorf("--token-env variable %q is empty", flags.staticTokenEnvName)
}
}
cred := tokenCredential(&oidctypes.Token{IDToken: &oidctypes.IDToken{Token: token}})
// Exchange that token with the concierge, if configured.
if concierge != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
var err error
cred, err = deps.exchangeToken(ctx, concierge, token)
if err != nil {
return fmt.Errorf("could not complete concierge credential exchange: %w", err)
}
}
return json.NewEncoder(out).Encode(cred)
}

View File

@@ -0,0 +1,195 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"context"
"crypto/x509/pkix"
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/conciergeclient"
)
func TestLoginStaticCommand(t *testing.T) {
testCA, err := certauthority.New(pkix.Name{CommonName: "Test CA"}, 1*time.Hour)
require.NoError(t, err)
tmpdir := testutil.TempDir(t)
testCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, ioutil.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
tests := []struct {
name string
args []string
env map[string]string
loginErr error
conciergeErr error
wantError bool
wantStdout string
wantStderr string
wantOptionsCount int
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: here.Doc(`
Login using a static token
Usage:
static [--token TOKEN] [--token-env TOKEN_NAME] [flags]
Flags:
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt')
--concierge-ca-bundle-data string CA bundle to use when connecting to the concierge
--concierge-endpoint string API base for the Pinniped concierge endpoint
--enable-concierge Exchange the token with the Pinniped concierge during login
-h, --help help for static
--token string Static token to present during login
--token-env string Environment variable containing a static token
`),
},
{
name: "missing required flags",
args: []string{},
wantError: true,
wantStderr: here.Doc(`
Error: one of --token or --token-env must be set
`),
},
{
name: "missing concierge flags",
args: []string{
"--token", "test-token",
"--enable-concierge",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid concierge parameters: endpoint must not be empty
`),
},
{
name: "missing env var",
args: []string{
"--token-env", "TEST_TOKEN_ENV",
},
wantError: true,
wantStderr: here.Doc(`
Error: --token-env variable "TEST_TOKEN_ENV" is not set
`),
},
{
name: "empty env var",
args: []string{
"--token-env", "TEST_TOKEN_ENV",
},
env: map[string]string{
"TEST_TOKEN_ENV": "",
},
wantError: true,
wantStderr: here.Doc(`
Error: --token-env variable "TEST_TOKEN_ENV" is empty
`),
},
{
name: "env var token success",
args: []string{
"--token-env", "TEST_TOKEN_ENV",
},
env: map[string]string{
"TEST_TOKEN_ENV": "test-token",
},
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
},
{
name: "concierge failure",
args: []string{
"--token", "test-token",
"--enable-concierge",
"--concierge-endpoint", "https://127.0.0.1/",
"--concierge-authenticator-type", "webhook",
"--concierge-authenticator-name", "test-authenticator",
},
conciergeErr: fmt.Errorf("some concierge error"),
wantError: true,
wantStderr: here.Doc(`
Error: could not complete concierge credential exchange: some concierge error
`),
},
{
name: "invalid api group suffix",
args: []string{
"--token", "test-token",
"--enable-concierge",
"--concierge-api-group-suffix", ".starts.with.dot",
"--concierge-authenticator-type", "jwt",
"--concierge-authenticator-name", "test-authenticator",
"--concierge-endpoint", "https://127.0.0.1:1234/",
},
wantError: true,
wantStderr: here.Doc(`
Error: invalid concierge parameters: invalid api group suffix: a lowercase RFC 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])?)*')
`),
},
{
name: "static token success",
args: []string{
"--token", "test-token",
},
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"token":"test-token"}}` + "\n",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
cmd := staticLoginCommand(staticLoginDeps{
lookupEnv: func(s string) (string, bool) {
v, ok := tt.env[s]
return v, ok
},
exchangeToken: func(ctx context.Context, client *conciergeclient.Client, token string) (*clientauthv1beta1.ExecCredential, error) {
require.Equal(t, token, "test-token")
if tt.conciergeErr != nil {
return nil, tt.conciergeErr
}
return &clientauthv1beta1.ExecCredential{
TypeMeta: metav1.TypeMeta{
Kind: "ExecCredential",
APIVersion: "client.authentication.k8s.io/v1beta1",
},
Status: &clientauthv1beta1.ExecCredentialStatus{
Token: "exchanged-token",
},
}, nil
},
})
require.NotNil(t, cmd)
var stdout, stderr bytes.Buffer
cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
cmd.SetArgs(tt.args)
err := cmd.Execute()
if tt.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
})
}
}

View File

@@ -1,13 +1,14 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"go.pinniped.dev/internal/plog"
)
//nolint: gochecknoglobals
@@ -18,11 +19,16 @@ var rootCmd = &cobra.Command{
SilenceUsage: true, // do not print usage message when commands fail
}
//nolint: gochecknoinits
func init() {
// We don't want klog flags showing up in our CLI.
plog.RemoveKlogGlobalFlags()
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -7,6 +7,7 @@ import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here"
@@ -36,13 +37,12 @@ var (
)
func TestNewVersionCmd(t *testing.T) {
t.Parallel()
tests := []struct {
name string
args []string
wantError bool
wantStdoutRegexp string
wantStderr string
wantStderrRegexp string
}{
{
name: "no flags",
@@ -58,13 +58,13 @@ func TestNewVersionCmd(t *testing.T) {
name: "arg passed",
args: []string{"tuna"},
wantError: true,
wantStdoutRegexp: `Error: unknown command "tuna" for "version"` + "\n" + knownGoodUsageRegexpForVersion,
wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cmd := newVersionCommand()
require.NotNil(t, cmd)
@@ -78,8 +78,8 @@ func TestNewVersionCmd(t *testing.T) {
} else {
require.NoError(t, err)
}
require.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
assert.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
assert.Regexp(t, tt.wantStderrRegexp, stderr.String(), "unexpected stderr")
})
}
}

View File

@@ -10,17 +10,17 @@ for details.
## Installing the Latest Version with Default Options
```bash
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/$(curl https://api.github.com/repos/vmware-tanzu/pinniped/releases/latest -s | jq .name -r)/install-pinniped-concierge.yaml
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml
```
## Installing an Older Version with Default Options
## Installing a Specific Version with Default Options
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
and use it to replace the version number in the URL below.
```bash
# Replace v0.2.0 with your preferred version in the URL below
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2.0/install-pinniped-concierge.yaml
# Replace v0.4.1 with your preferred version in the URL below
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-concierge.yaml
```
## Installing with Custom Options

View File

@@ -0,0 +1,171 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: jwtauthenticators.authentication.concierge.pinniped.dev
spec:
group: authentication.concierge.pinniped.dev
names:
categories:
- pinniped
- pinniped-authenticator
- pinniped-authenticators
kind: JWTAuthenticator
listKind: JWTAuthenticatorList
plural: jwtauthenticators
singular: jwtauthenticator
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.issuer
name: Issuer
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: "JWTAuthenticator describes the configuration of a JWT authenticator.
\n Upon receiving a signed JWT, a JWTAuthenticator will performs some validation
on it (e.g., valid signature, existence of claims, etc.) and extract the
username and groups from the token."
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the authenticator.
properties:
audience:
description: Audience is the required value of the "aud" JWT claim.
minLength: 1
type: string
claims:
description: Claims allows customization of the claims that will be
mapped to user identity for Kubernetes access.
properties:
groups:
description: Groups is the name of the claim which should be read
to extract the user's group membership from the JWT token. When
not specified, it will default to "groups".
type: string
username:
description: Username is the name of the claim which should be
read to extract the username from the JWT token. When not specified,
it will default to "username".
type: string
type: object
issuer:
description: Issuer is the OIDC issuer URL that will be used to discover
public signing keys. Issuer is also used to validate the "iss" JWT
claim.
minLength: 1
pattern: ^https://
type: string
tls:
description: TLS configuration for communicating with the OIDC provider.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
required:
- audience
- issuer
type: object
status:
description: Status of the authenticator.
properties:
conditions:
description: Represents the observations of the authenticator's current
state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -11,14 +11,14 @@ spec:
group: authentication.concierge.pinniped.dev
names:
categories:
- all
- authenticator
- authenticators
- pinniped
- pinniped-authenticator
- pinniped-authenticators
kind: WebhookAuthenticator
listKind: WebhookAuthenticatorList
plural: webhookauthenticators
singular: webhookauthenticator
scope: Namespaced
scope: Cluster
versions:
- additionalPrinterColumns:
- jsonPath: .spec.endpoint
@@ -137,7 +137,8 @@ spec:
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -10,15 +10,18 @@ metadata:
spec:
group: config.concierge.pinniped.dev
names:
categories:
- pinniped
kind: CredentialIssuer
listKind: CredentialIssuerList
plural: credentialissuers
singular: credentialissuer
scope: Namespaced
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Describes the configuration status of a Pinniped credential issuer.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@@ -95,11 +98,11 @@ spec:
required:
- strategies
type: object
required:
- status
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -1,9 +1,9 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:json", "json")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel", "pinnipedDevAPIGroupWithPrefix")
#@ if not data.values.into_namespace:
---
@@ -37,6 +37,7 @@ data:
servingCertificate:
durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @)
renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @)
apiGroupSuffix: (@= data.values.api_group_suffix @)
names:
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
@@ -57,6 +58,9 @@ data:
imagePullSecrets:
- image-pull-secret
(@ end @)
(@ if data.values.log_level: @)
logLevel: (@= getAndValidateLogLevel() @)
(@ end @)
---
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
apiVersion: v1
@@ -87,8 +91,8 @@ spec:
scheduler.alpha.kubernetes.io/critical-pod: ""
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
runAsUser: #@ data.values.run_as_user
runAsGroup: #@ data.values.run_as_group
serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:
@@ -145,6 +149,9 @@ spec:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "name"
fieldRef:
fieldPath: metadata.name
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
@@ -185,11 +192,11 @@ spec:
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1alpha1.login.concierge.pinniped.dev
name: #@ pinnipedDevAPIGroupWithPrefix("v1alpha1.login.concierge")
labels: #@ labels()
spec:
version: v1alpha1
group: login.concierge.pinniped.dev
group: #@ pinnipedDevAPIGroupWithPrefix("login.concierge")
groupPriorityMinimum: 2500
versionPriority: 10
#! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code.

View File

@@ -1,4 +1,4 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
@@ -12,6 +12,10 @@
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def pinnipedDevAPIGroupWithPrefix(prefix):
#@ return prefix + "." + data.values.api_group_suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
@@ -28,3 +32,11 @@ app: #@ data.values.app_name
_: #@ template.replace(defaultLabel())
_: #@ template.replace(data.values.custom_labels)
#@ end
#@ def getAndValidateLogLevel():
#@ log_level = data.values.log_level
#@ if log_level != "info" and log_level != "debug" and log_level != "trace" and log_level != "all":
#@ fail("log_level '" + log_level + "' is invalid")
#@ end
#@ return log_level
#@ end

View File

@@ -1,8 +1,8 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "pinnipedDevAPIGroupWithPrefix")
#! Give permission to various cluster-scoped objects
---
@@ -17,13 +17,32 @@ rules:
verbs: [ get, list, watch ]
- apiGroups: [ apiregistration.k8s.io ]
resources: [ apiservices ]
verbs: [ create, get, list, patch, update, watch ]
verbs: [ get, list, patch, update, watch ]
- apiGroups: [ admissionregistration.k8s.io ]
resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations ]
verbs: [ get, list, watch ]
- apiGroups: [ flowcontrol.apiserver.k8s.io ]
resources: [ flowschemas, prioritylevelconfigurations ]
verbs: [ get, list, watch ]
- apiGroups: [ policy ]
resources: [ podsecuritypolicies ]
verbs: [ use ]
- apiGroups: [ security.openshift.io ]
resources: [ securitycontextconstraints ]
verbs: [ use ]
resourceNames: [ nonroot ]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
resources: [ credentialissuers ]
verbs: [ get, list, watch, create ]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
resources: [ credentialissuers/status ]
verbs: [get, patch, update]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
resources: [ jwtauthenticators, webhookauthenticators ]
verbs: [ get, list, watch ]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
@@ -62,9 +81,9 @@ rules:
- apiGroups: [ "" ]
resources: [ pods/exec ]
verbs: [ create ]
- apiGroups: [ config.concierge.pinniped.dev, authentication.concierge.pinniped.dev ]
resources: [ "*" ]
verbs: [ create, get, list, update, watch ]
- apiGroups: [apps]
resources: [replicasets,deployments]
verbs: [get]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
@@ -117,7 +136,8 @@ metadata:
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
labels: #@ labels()
rules:
- apiGroups: [ login.concierge.pinniped.dev ]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("login.concierge")
resources: [ tokencredentialrequests ]
verbs: [ create ]
---

View File

@@ -1,4 +1,4 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
@@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
@@ -50,3 +50,16 @@ discovery_url: #! e.g., https://example.com
#! about every 25 days.
api_serving_certificate_duration_seconds: 2592000
api_serving_certificate_renew_before_seconds: 2160000
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer
#! information), trace (timing information), all (kitchen sink).
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
#! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then
#! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc.
api_group_suffix: pinniped.dev

View File

@@ -1,17 +1,33 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:overlay", "overlay")
#@ load("helpers.lib.yaml", "labels")
#@ load("helpers.lib.yaml", "labels", "pinnipedDevAPIGroupWithPrefix")
#@ load("@ytt:data", "data")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"credentialissuers.config.concierge.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("credentialissuers.config.concierge")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("config.concierge")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"webhookauthenticators.authentication.concierge.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("webhookauthenticators.authentication.concierge")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"jwtauthenticators.authentication.concierge.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("jwtauthenticators.authentication.concierge")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("authentication.concierge")

View File

@@ -15,17 +15,17 @@ User accounts can be created and edited dynamically using `kubectl` commands (se
## Installing the Latest Version with Default Options
```bash
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/latest/download/install-local-user-authenticator.yaml
kubectl apply -f https://get.pinniped.dev/latest/install-local-user-authenticator.yaml
```
## Installing an Older Version with Default Options
## Installing a Specific Version with Default Options
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
and use it to replace the version number in the URL below.
```bash
# Replace v0.2.0 with your preferred version in the URL below
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2.0/install-local-user-authenticator.yaml
# Replace v0.4.1 with your preferred version in the URL below
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-local-user-authenticator.yaml
```
## Installing with Custom Options
@@ -79,7 +79,7 @@ kubectl get secret local-user-authenticator-tls-serving-certificate --namespace
When installing Pinniped on the same cluster, configure local-user-authenticator as an Identity Provider for Pinniped
using the webhook URL `https://local-user-authenticator.local-user-authenticator.svc/authenticate`
along with the CA bundle fetched by the above command. See [doc/demo.md](../../doc/demo.md) for an example.
along with the CA bundle fetched by the above command. See [demo](https://pinniped.dev/docs/demo/) for an example.
## Optional: Manually Testing the Webhook Endpoint After Installing

View File

@@ -1,4 +1,4 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
@@ -48,8 +48,8 @@ spec:
app: local-user-authenticator
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
runAsUser: #@ data.values.run_as_user
runAsGroup: #@ data.values.run_as_group
serviceAccountName: local-user-authenticator
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:

View File

@@ -1,11 +1,11 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
---
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
@@ -14,3 +14,6 @@ image_tag: latest
#! Typically the value would be the output of: kubectl create secret docker-registry x --docker-server=https://example.io --docker-username="USERNAME" --docker-password="PASSWORD" --dry-run=client -o json | jq -r '.data[".dockerconfigjson"]'
#! Optional.
image_pull_dockerconfigjson: #! e.g. {"auths":{"https://registry.example.com":{"username":"USERNAME","password":"PASSWORD","auth":"BASE64_ENCODED_USERNAME_COLON_PASSWORD"}}}
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process

View File

@@ -8,17 +8,17 @@ It can be deployed when those features are needed.
## Installing the Latest Version with Default Options
```bash
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/latest/download/install-pinniped-supervisor.yaml
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-supervisor.yaml
```
## Installing an Older Version with Default Options
## Installing a Specific Version with Default Options
Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
and use it to replace the version number in the URL below.
```bash
# Replace v0.3.0 with your preferred version in the URL below
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.3.0/install-pinniped-supervisor.yaml
# Replace v0.4.1 with your preferred version in the URL below
kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-supervisor.yaml
```
## Installing with Custom Options
@@ -59,7 +59,7 @@ The most common ways are:
1. Or, define a [TCP LoadBalancer Service](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
which is a layer 4 load balancer and does not terminate TLS. In this case, the Supervisor app will need to be
configured with TLS certificates and will terminate the TLS connection itself (see the section about OIDCProvider
configured with TLS certificates and will terminate the TLS connection itself (see the section about FederationDomain
below). The LoadBalancer Service should be configured to use the HTTPS port 443 of the Supervisor pods as its `targetPort`.
*Warning:* Do not expose the Supervisor's port 8080 to the public. It would not be secure for the OIDC protocol
@@ -132,12 +132,12 @@ spec:
### Configuring the Supervisor to Act as an OIDC Provider
The Supervisor can be configured as an OIDC provider by creating `OIDCProvider` resources
The Supervisor can be configured as an OIDC provider by creating `FederationDomain` resources
in the same namespace where the Supervisor app was installed. For example:
```yaml
apiVersion: config.supervisor.pinniped.dev/v1alpha1
kind: OIDCProvider
kind: FederationDomain
metadata:
name: my-provider
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
@@ -156,12 +156,12 @@ spec:
#### Configuring TLS for the Supervisor OIDC Endpoints
If you have terminated TLS outside the app, for example using an Ingress with TLS certificates, then you do not need to
configure TLS certificates on the OIDCProvider.
configure TLS certificates on the FederationDomain.
If you are using a LoadBalancer Service to expose the Supervisor app outside your cluster, then you will
also need to configure the Supervisor app to terminate TLS. There are two places to configure TLS certificates:
1. Each `OIDCProvider` can be configured with TLS certificates, using the `spec.tls.secretName` field.
1. Each `FederationDomain` can be configured with TLS certificates, using the `spec.tls.secretName` field.
1. The default TLS certificate for all OIDC providers can be configured by creating a Secret called
`pinniped-supervisor-default-tls-certificate` in the same namespace in which the Supervisor was installed.

View File

@@ -6,20 +6,22 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: oidcproviders.config.supervisor.pinniped.dev
name: federationdomains.config.supervisor.pinniped.dev
spec:
group: config.supervisor.pinniped.dev
names:
kind: OIDCProvider
listKind: OIDCProviderList
plural: oidcproviders
singular: oidcprovider
categories:
- pinniped
kind: FederationDomain
listKind: FederationDomainList
plural: federationdomains
singular: federationdomain
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: OIDCProvider describes the configuration of an OIDC provider.
description: FederationDomain describes the configuration of an OIDC provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@@ -48,14 +50,14 @@ spec:
minLength: 1
type: string
tls:
description: TLS configures how this OIDCProvider is served over Transport
Layer Security (TLS).
description: TLS configures how this FederationDomain is served over
Transport Layer Security (TLS).
properties:
secretName:
description: "SecretName is an optional name of a Secret in the
same namespace, of type `kubernetes.io/tls`, which contains
the TLS serving certificate for the HTTPS endpoints served by
this OIDCProvider. When provided, the TLS Secret named here
this FederationDomain. When provided, the TLS Secret named here
must contain keys named `tls.crt` and `tls.key` that contain
the certificate and private key to use for TLS. \n Server Name
Indication (SNI) is an extension to the Transport Layer Security
@@ -79,17 +81,6 @@ spec:
status:
description: Status of the OIDC provider.
properties:
jwksSecret:
description: JWKSSecret holds the name of the secret in which this
OIDC Provider's signing/verification keys are stored. If it is empty,
then the signing/verification keys are either unknown or they don't
exist.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
@@ -99,6 +90,51 @@ spec:
message:
description: Message provides human-readable details about the Status.
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
secrets.
properties:
jwks:
description: JWKS holds the name of the corev1.Secret in which
this OIDC Provider's signing/verification keys are stored. If
it is empty, then the signing/verification keys are either unknown
or they don't exist.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
stateEncryptionKey:
description: StateSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for encrypting state parameters
is stored.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
stateSigningKey:
description: StateSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for signing state parameters
is stored.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
tokenSigningKey:
description: TokenSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for signing tokens is stored.
properties:
name:
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?'
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
@@ -106,6 +142,7 @@ spec:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
@@ -113,6 +150,8 @@ spec:
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""

View File

@@ -1,9 +1,9 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:json", "json")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "getAndValidateLogLevel")
#@ if not data.values.into_namespace:
---
@@ -30,9 +30,13 @@ metadata:
data:
#@yaml/text-templated-strings
pinniped.yaml: |
apiGroupSuffix: (@= data.values.api_group_suffix @)
names:
defaultTLSCertificateSecret: (@= defaultResourceNameWithSuffix("default-tls-certificate") @)
labels: (@= json.encode(labels()).rstrip() @)
(@ if data.values.log_level: @)
logLevel: (@= getAndValidateLogLevel() @)
(@ end @)
---
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
apiVersion: v1
@@ -61,8 +65,8 @@ spec:
labels: #@ defaultLabel()
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
runAsUser: #@ data.values.run_as_user
runAsGroup: #@ data.values.run_as_group
serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:
@@ -129,6 +133,9 @@ spec:
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
- path: "name"
fieldRef:
fieldPath: metadata.name
#! This will help make sure our multiple pods run on different nodes, making
#! our deployment "more" "HA".
affinity:

View File

@@ -1,4 +1,4 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
@@ -12,6 +12,10 @@
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def pinnipedDevAPIGroupWithPrefix(prefix):
#@ return prefix + "." + data.values.api_group_suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
@@ -28,3 +32,11 @@ app: #@ data.values.app_name
_: #@ template.replace(defaultLabel())
_: #@ template.replace(data.values.custom_labels)
#@ end
#@ def getAndValidateLogLevel():
#@ log_level = data.values.log_level
#@ if log_level != "info" and log_level != "debug" and log_level != "trace" and log_level != "all":
#@ fail("log_level '" + log_level + "' is invalid")
#@ end
#@ return log_level
#@ end

View File

@@ -0,0 +1,205 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: oidcidentityproviders.idp.supervisor.pinniped.dev
spec:
group: idp.supervisor.pinniped.dev
names:
categories:
- pinniped
- pinniped-idp
- pinniped-idps
kind: OIDCIdentityProvider
listKind: OIDCIdentityProviderList
plural: oidcidentityproviders
singular: oidcidentityprovider
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.phase
name: Status
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: OIDCIdentityProvider describes the configuration of an upstream
OpenID Connect identity provider.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
properties:
authorizationConfig:
description: AuthorizationConfig holds information about how to form
the OAuth2 authorization request parameters to be used with this
OIDC identity provider.
properties:
additionalScopes:
description: AdditionalScopes are the scopes in addition to "openid"
that will be requested as part of the authorization request
flow with an OIDC identity provider. By default only the "openid"
scope will be requested.
items:
type: string
type: array
type: object
claims:
description: Claims provides the names of token claims that will be
used when inspecting an identity from this OIDC identity provider.
properties:
groups:
description: Groups provides the name of the token claim that
will be used to ascertain the groups to which an identity belongs.
type: string
username:
description: Username provides the name of the token claim that
will be used to ascertain an identity's username.
type: string
type: object
client:
description: OIDCClient contains OIDC client information to be used
used with this OIDC identity provider.
properties:
secretName:
description: SecretName contains the name of a namespace-local
Secret object that provides the clientID and clientSecret for
an OIDC client. If only the SecretName is specified in an OIDCClient
struct, then it is expected that the Secret is of type "secrets.pinniped.dev/oidc-client"
with keys "clientID" and "clientSecret".
type: string
required:
- secretName
type: object
issuer:
description: Issuer is the issuer URL of this OIDC identity provider,
i.e., where to fetch /.well-known/openid-configuration.
minLength: 1
pattern: ^https://
type: string
tls:
description: TLS configuration for discovery/JWKS requests to the
issuer.
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM bundle).
If omitted, a default set of system roots will be trusted.
type: string
type: object
required:
- client
- issuer
type: object
status:
description: Status of the identity provider.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the OIDCIdentityProvider.
enum:
- Pending
- Ready
- Error
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -1,8 +1,8 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix", "pinnipedDevAPIGroupWithPrefix")
#! Give permission to various objects within the app's own namespace
---
@@ -16,9 +16,30 @@ rules:
- apiGroups: [""]
resources: [secrets]
verbs: [create, get, list, patch, update, watch, delete]
- apiGroups: [config.supervisor.pinniped.dev]
resources: [oidcproviders]
verbs: [update, get, list, watch]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
resources: [federationdomains]
verbs: [get, list, watch]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
resources: [federationdomains/status]
verbs: [get, patch, update]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
resources: [oidcidentityproviders]
verbs: [get, list, watch]
- apiGroups:
- #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")
resources: [oidcidentityproviders/status]
verbs: [get, patch, update]
#! We want to be able to read pods/replicasets/deployment so we can learn who our deployment is to set
#! as an owner reference.
- apiGroups: [""]
resources: [pods]
verbs: [get]
- apiGroups: [apps]
resources: [replicasets,deployments]
verbs: [get]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1

View File

@@ -1,4 +1,4 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
@@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
@@ -52,3 +52,16 @@ service_https_clusterip_port: #! when specified, creates a ClusterIP Service wit
#! Ignored unless service_http_loadbalancer_port and/or service_https_loadbalancer_port are provided.
#! Optional.
service_loadbalancer_ip: #! e.g. 1.2.3.4
#! Specify the verbosity of logging: info ("nice to know" information), debug (developer
#! information), trace (timing information), all (kitchen sink).
log_level: #! By default, when this value is left unset, only warnings and errors are printed. There is no way to suppress warning and error logs.
run_as_user: 1001 #! run_as_user specifies the user ID that will own the local-user-authenticator process
run_as_group: 1001 #! run_as_group specifies the group ID that will own the local-user-authenticator process
#! Specify the API group suffix for all Pinniped API groups. By default, this is set to
#! pinniped.dev, so Pinniped API groups will look like foo.pinniped.dev,
#! authentication.concierge.pinniped.dev, etc. As an example, if this is set to tuna.io, then
#! Pinniped API groups will look like foo.tuna.io. authentication.concierge.tuna.io, etc.
api_group_suffix: pinniped.dev

View File

@@ -1,11 +1,24 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:overlay", "overlay")
#@ load("helpers.lib.yaml", "labels")
#@ load("helpers.lib.yaml", "labels", "pinnipedDevAPIGroupWithPrefix")
#@ load("@ytt:data", "data")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"oidcproviders.config.supervisor.pinniped.dev"}}), expects=1
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"federationdomains.config.supervisor.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("federationdomains.config.supervisor")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("config.supervisor")
#@overlay/match by=overlay.subset({"kind": "CustomResourceDefinition", "metadata":{"name":"oidcidentityproviders.idp.supervisor.pinniped.dev"}}), expects=1
---
metadata:
#@overlay/match missing_ok=True
labels: #@ labels()
name: #@ pinnipedDevAPIGroupWithPrefix("oidcidentityproviders.idp.supervisor")
spec:
group: #@ pinnipedDevAPIGroupWithPrefix("idp.supervisor")

View File

@@ -1,75 +0,0 @@
# Architecture
The principal purpose of Pinniped is to allow users to access Kubernetes
clusters. Pinniped hopes to enable this access across a wide range of Kubernetes
environments with zero configuration.
This integration is implemented using a credential exchange API which takes as
input a credential from the external IDP and returns a credential which is understood by the host
Kubernetes cluster.
<img src="img/pinniped_architecture.svg" alt="Pinniped Architecture Sketch" width="300px"/>
Pinniped supports various IDP types and implements different integration strategies
for various Kubernetes distributions to make authentication possible.
## Supported Kubernetes Cluster Types
Pinniped supports the following types of Kubernetes clusters:
- Clusters where the Kube Controller Manager pod is accessible from Pinniped's pods.
Support for other types of Kubernetes distributions is coming soon.
## External Identity Provider Integrations
Pinniped will consume identity from one or more external identity providers
(IDPs). Administrators will configure external IDPs via Kubernetes custom
resources allowing Pinniped to be managed using GitOps and standard Kubernetes tools.
Pinniped supports the following external IDP types.
1. Any webhook which implements the
[Kubernetes TokenReview API](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication).
In addition to allowing the integration of any existing IDP which implements this API, webhooks also
serve as an extension point for Pinniped by allowing for integration of arbitrary custom authenticators.
While a custom implementation may be in any language or framework, this project provides a
sample implementation in Golang. See the `ServeHTTP` method of
[cmd/local-user-authenticator/main.go](../cmd/local-user-authenticator/main.go).
More IDP types are coming soon.
## Cluster Integration Strategies
Pinniped will issue a cluster credential by leveraging cluster-specific
functionality. In the near term, cluster integrations will happen via different
cluster-specific flows depending on the type of cluster. In the longer term,
Pinniped hopes to contribute and leverage upstream Kubernetes extension points that
cleanly enable this integration.
Pinniped supports the following cluster integration strategies.
1. Pinniped hosts a credential exchange API endpoint via a Kubernetes aggregated API server.
This API returns a new cluster-specific credential using the cluster's signing keypair to
issue short-lived cluster certificates. (In the future, when the Kubernetes CSR API
provides a way to issue short-lived certificates, then the Pinniped credential exchange API
will use that instead of using the cluster's signing keypair.)
More cluster integration strategies are coming soon, which will allow Pinniped to
support more Kubernetes cluster types.
## `kubectl` Integration
With any of the above IDPs and integration strategies, `kubectl` commands receive the
cluster-specific credential via a
[Kubernetes client-go credential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins).
Users may use the Pinniped CLI as the credential plugin, or they may use any proprietary CLI
built with the [Pinniped Go client library](../generated).
## Example Cluster Authentication Sequence Diagram
This diagram demonstrates using `kubectl get pods` with the Pinniped CLI configured as the credential plugin,
and with a webhook IDP configured as the identity provider for the Pinniped server.
![example-cluster-authentication-sequence-diagram](img/pinniped.svg)

View File

@@ -1,12 +0,0 @@
# `doc/img` README
## How to Update these Images
- [pinniped.svg](pinniped.svg) was generated using [`plantuml`](https://plantuml.com/).
To regenerate the image, run `plantuml -tsvg pinniped.txt` from this directory.
- [pinniped_architecture.svg](pinniped_architecture.svg) was created on [draw.io](https://draw.io).
It can be opened again for editing on that site by choosing "File" -> "Open from" -> "Device".
Because it includes embedded icons it should be exported using "File" -> "Export as" -> "SVG",
with the "Transparent Background", "Embed Images", and "Include a copy of my diagram" options
checked. The icons in this diagram are from their "CAE" shapes set.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -6,9 +6,9 @@
.Packages
- xref:{anchor_prefix}-authentication-concierge-pinniped-dev-v1alpha1[$$authentication.concierge.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-config-concierge-pinniped-dev-config[$$config.concierge.pinniped.dev/config$$]
- xref:{anchor_prefix}-config-concierge-pinniped-dev-v1alpha1[$$config.concierge.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-config-supervisor-pinniped-dev-v1alpha1[$$config.supervisor.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-idp-supervisor-pinniped-dev-v1alpha1[$$idp.supervisor.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-login-concierge-pinniped-dev-v1alpha1[$$login.concierge.pinniped.dev/v1alpha1$$]
@@ -22,10 +22,11 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authenticatio
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
****
@@ -41,13 +42,104 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec"]
==== TLSSpec
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-conditionstatus"]
==== ConditionStatus (string)
Configuration for configuring TLS on various authenticators.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$]
****
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator"]
==== JWTAuthenticator
JWTAuthenticator describes the configuration of a JWT authenticator.
Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid signature, existence of claims, etc.) and extract the username and groups from the token.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorlist[$$JWTAuthenticatorList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]__ | Spec for configuring the authenticator.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus[$$JWTAuthenticatorStatus$$]__ | Status of the authenticator.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec"]
==== JWTAuthenticatorSpec
Spec for configuring a JWT authenticator.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`issuer`* __string__ | Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is also used to validate the "iss" JWT claim.
| *`audience`* __string__ | Audience is the required value of the "aud" JWT claim.
| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwttokenclaims[$$JWTTokenClaims$$]__ | Claims allows customization of the claims that will be mapped to user identity for Kubernetes access.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for communicating with the OIDC provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorstatus"]
==== JWTAuthenticatorStatus
Status of a JWT authenticator.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticator[$$JWTAuthenticator$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwttokenclaims"]
==== JWTTokenClaims
JWTTokenClaims allows customization of the claims that will be mapped to user identity for Kubernetes access.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups is the name of the claim which should be read to extract the user's group membership from the JWT token. When not specified, it will default to "groups".
| *`username`* __string__ | Username is the name of the claim which should be read to extract the username from the JWT token. When not specified, it will default to "username".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec"]
==== TLSSpec
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-jwtauthenticatorspec[$$JWTAuthenticatorSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec[$$WebhookAuthenticatorSpec$$]
****
@@ -111,19 +203,11 @@ Status of a webhook authenticator.
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$]__ | Represents the observations of the authenticator's current state.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-condition[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|===
[id="{anchor_prefix}-config-concierge-pinniped-dev-config"]
=== config.concierge.pinniped.dev/config
Package config is the internal version of the Pinniped concierge configuration API.
[id="{anchor_prefix}-config-concierge-pinniped-dev-v1alpha1"]
=== config.concierge.pinniped.dev/v1alpha1
@@ -134,7 +218,7 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer"]
==== CredentialIssuer
Describes the configuration status of a Pinniped credential issuer.
.Appears In:
****
@@ -217,14 +301,14 @@ Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor configuratio
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcprovider"]
==== OIDCProvider
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomain"]
==== FederationDomain
OIDCProvider describes the configuration of an OIDC provider.
FederationDomain describes the configuration of an OIDC provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderlist[$$OIDCProviderList$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainlist[$$FederationDomainList$$]
****
[cols="25a,75a", options="header"]
@@ -232,21 +316,41 @@ OIDCProvider describes the configuration of an OIDC provider.
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderspec[$$OIDCProviderSpec$$]__ | Spec of the OIDC provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderstatus[$$OIDCProviderStatus$$]__ | Status of the OIDC provider.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$]__ | Spec of the OIDC provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainstatus[$$FederationDomainStatus$$]__ | Status of the OIDC provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderspec"]
==== OIDCProviderSpec
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainsecrets"]
==== FederationDomainSecrets
OIDCProviderSpec is a struct that describes an OIDC Provider.
FederationDomainSecrets holds information about this OIDC Provider's secrets.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcprovider[$$OIDCProvider$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainstatus[$$FederationDomainStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`jwks`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | JWKS holds the name of the corev1.Secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.
| *`tokenSigningKey`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | TokenSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for signing tokens is stored.
| *`stateSigningKey`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for signing state parameters is stored.
| *`stateEncryptionKey`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for encrypting state parameters is stored.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainspec"]
==== FederationDomainSpec
FederationDomainSpec is a struct that describes an OIDC Provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomain[$$FederationDomain$$]
****
[cols="25a,75a", options="header"]
@@ -254,44 +358,44 @@ OIDCProviderSpec is a struct that describes an OIDC Provider.
| Field | Description
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcprovidertlsspec[$$OIDCProviderTLSSpec$$]__ | TLS configures how this OIDCProvider is served over Transport Layer Security (TLS).
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintlsspec[$$FederationDomainTLSSpec$$]__ | TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderstatus"]
==== OIDCProviderStatus
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainstatus"]
==== FederationDomainStatus
OIDCProviderStatus is a struct that describes the actual state of an OIDC Provider.
FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcprovider[$$OIDCProvider$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomain[$$FederationDomain$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __OIDCProviderStatusCondition__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`status`* __FederationDomainStatusCondition__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`jwksSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys are stored. If it is empty, then the signing/verification keys are either unknown or they don't exist.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcprovidertlsspec"]
==== OIDCProviderTLSSpec
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomaintlsspec"]
==== FederationDomainTLSSpec
OIDCProviderTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-oidcproviderspec[$$OIDCProviderSpec$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainspec[$$FederationDomainSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDCProvider. When provided, the TLS Secret named here must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this FederationDomain. When provided, the TLS Secret named here must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
Server Name Indication (SNI) is an extension to the Transport Layer Security (TLS) supported by all major browsers.
SecretName is required if you would like to use different TLS certificates for issuers of different hostnames. SNI requests do not include port numbers, so all issuers with the same DNS hostname must use the same SecretName value even if they have different port numbers.
SecretName is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). It is also not required when you would like all requests to this OIDC Provider's HTTPS endpoints to use the default TLS certificate, which is configured elsewhere.
@@ -300,6 +404,166 @@ OIDCProviderTLSSpec is a struct that describes the TLS configuration for an OIDC
[id="{anchor_prefix}-idp-supervisor-pinniped-dev-v1alpha1"]
=== idp.supervisor.pinniped.dev/v1alpha1
Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor identity provider (IDP) API.
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition"]
==== Condition
Condition status of a resource (mirrored from the metav1.Condition type added in Kubernetes 1.19). In a future API version we can switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`type`* __string__ | type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
| *`status`* __ConditionStatus__ | status of the condition, one of True, False, Unknown.
| *`observedGeneration`* __integer__ | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
| *`lastTransitionTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
| *`reason`* __string__ | reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
| *`message`* __string__ | message is a human readable message indicating details about the transition. This may be an empty string.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig"]
==== OIDCAuthorizationConfig
OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization request parameters.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`additionalScopes`* __string array__ | AdditionalScopes are the scopes in addition to "openid" that will be requested as part of the authorization request flow with an OIDC identity provider. By default only the "openid" scope will be requested.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcclaims"]
==== OIDCClaims
OIDCClaims provides a mapping from upstream claims into identities.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`groups`* __string__ | Groups provides the name of the token claim that will be used to ascertain the groups to which an identity belongs.
| *`username`* __string__ | Username provides the name of the token claim that will be used to ascertain an identity's username.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcclient"]
==== OIDCClient
OIDCClient contains information about an OIDC client (e.g., client ID and client secret).
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`secretName`* __string__ | SecretName contains the name of a namespace-local Secret object that provides the clientID and clientSecret for an OIDC client. If only the SecretName is specified in an OIDCClient struct, then it is expected that the Secret is of type "secrets.pinniped.dev/oidc-client" with keys "clientID" and "clientSecret".
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityprovider"]
==== OIDCIdentityProvider
OIDCIdentityProvider describes the configuration of an upstream OpenID Connect identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderlist[$$OIDCIdentityProviderList$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#objectmeta-v1-meta[$$ObjectMeta$$]__ | Refer to Kubernetes API documentation for fields of `metadata`.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus[$$OIDCIdentityProviderStatus$$]__ | Status of the identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec"]
==== OIDCIdentityProviderSpec
Spec for configuring an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityprovider[$$OIDCIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`issuer`* __string__ | Issuer is the issuer URL of this OIDC identity provider, i.e., where to fetch /.well-known/openid-configuration.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration for discovery/JWKS requests to the issuer.
| *`authorizationConfig`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcauthorizationconfig[$$OIDCAuthorizationConfig$$]__ | AuthorizationConfig holds information about how to form the OAuth2 authorization request parameters to be used with this OIDC identity provider.
| *`claims`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcclaims[$$OIDCClaims$$]__ | Claims provides the names of token claims that will be used when inspecting an identity from this OIDC identity provider.
| *`client`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcclient[$$OIDCClient$$]__ | OIDCClient contains OIDC client information to be used used with this OIDC identity provider.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderstatus"]
==== OIDCIdentityProviderStatus
Status of an OIDC identity provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityprovider[$$OIDCIdentityProvider$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`phase`* __OIDCIdentityProviderPhase__ | Phase summarizes the overall status of the OIDCIdentityProvider.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider's current state.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-tlsspec"]
==== TLSSpec
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-idp-v1alpha1-oidcidentityproviderspec[$$OIDCIdentityProviderSpec$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`certificateAuthorityData`* __string__ | X.509 Certificate Authority (base64-encoded PEM bundle). If omitted, a default set of system roots will be trusted.
|===
[id="{anchor_prefix}-login-concierge-pinniped-dev-v1alpha1"]
=== login.concierge.pinniped.dev/v1alpha1

View File

@@ -1,8 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=authentication.concierge.pinniped.dev
// Package authentication is the internal version of the Pinniped concierge authentication API.
package authentication

View File

@@ -3,7 +3,6 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/generated/1.17/apis/concierge/authentication
// +k8s:defaulter-gen=TypeMeta
// +groupName=authentication.concierge.pinniped.dev

View File

@@ -24,7 +24,7 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.
@@ -32,6 +32,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&WebhookAuthenticator{},
&WebhookAuthenticatorList{},
&JWTAuthenticator{},
&JWTAuthenticatorList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -0,0 +1,83 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// Status of a JWT authenticator.
type JWTAuthenticatorStatus struct {
// Represents the observations of the authenticator's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
// Spec for configuring a JWT authenticator.
type JWTAuthenticatorSpec struct {
// Issuer is the OIDC issuer URL that will be used to discover public signing keys. Issuer is
// also used to validate the "iss" JWT claim.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^https://`
Issuer string `json:"issuer"`
// Audience is the required value of the "aud" JWT claim.
// +kubebuilder:validation:MinLength=1
Audience string `json:"audience"`
// Claims allows customization of the claims that will be mapped to user identity
// for Kubernetes access.
// +optional
Claims JWTTokenClaims `json:"claims"`
// TLS configuration for communicating with the OIDC provider.
// +optional
TLS *TLSSpec `json:"tls,omitempty"`
}
// JWTTokenClaims allows customization of the claims that will be mapped to user identity
// for Kubernetes access.
type JWTTokenClaims struct {
// Groups is the name of the claim which should be read to extract the user's
// group membership from the JWT token. When not specified, it will default to "groups".
// +optional
Groups string `json:"groups"`
// Username is the name of the claim which should be read to extract the
// username from the JWT token. When not specified, it will default to "username".
// +optional
Username string `json:"username"`
}
// JWTAuthenticator describes the configuration of a JWT authenticator.
//
// Upon receiving a signed JWT, a JWTAuthenticator will performs some validation on it (e.g., valid
// signature, existence of claims, etc.) and extract the username and groups from the token.
//
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:subresource:status
type JWTAuthenticator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the authenticator.
Spec JWTAuthenticatorSpec `json:"spec"`
// Status of the authenticator.
Status JWTAuthenticatorStatus `json:"status,omitempty"`
}
// List of JWTAuthenticator objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type JWTAuthenticatorList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []JWTAuthenticator `json:"items"`
}

View File

@@ -29,9 +29,11 @@ type WebhookAuthenticatorSpec struct {
// WebhookAuthenticator describes the configuration of a webhook authenticator.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=all;authenticator;authenticators
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators,scope=Cluster
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
// +kubebuilder:subresource:status
type WebhookAuthenticator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

View File

@@ -1,22 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
return nil
}

View File

@@ -28,6 +28,128 @@ func (in *Condition) DeepCopy() *Condition {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticator.
func (in *JWTAuthenticator) DeepCopy() *JWTAuthenticator {
if in == nil {
return nil
}
out := new(JWTAuthenticator)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *JWTAuthenticator) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTAuthenticatorList) DeepCopyInto(out *JWTAuthenticatorList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]JWTAuthenticator, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorList.
func (in *JWTAuthenticatorList) DeepCopy() *JWTAuthenticatorList {
if in == nil {
return nil
}
out := new(JWTAuthenticatorList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *JWTAuthenticatorList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTAuthenticatorSpec) DeepCopyInto(out *JWTAuthenticatorSpec) {
*out = *in
out.Claims = in.Claims
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(TLSSpec)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorSpec.
func (in *JWTAuthenticatorSpec) DeepCopy() *JWTAuthenticatorSpec {
if in == nil {
return nil
}
out := new(JWTAuthenticatorSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTAuthenticatorStatus) DeepCopyInto(out *JWTAuthenticatorStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTAuthenticatorStatus.
func (in *JWTAuthenticatorStatus) DeepCopy() *JWTAuthenticatorStatus {
if in == nil {
return nil
}
out := new(JWTAuthenticatorStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTTokenClaims) DeepCopyInto(out *JWTTokenClaims) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTTokenClaims.
func (in *JWTTokenClaims) DeepCopy() *JWTTokenClaims {
if in == nil {
return nil
}
out := new(JWTTokenClaims)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSSpec) DeepCopyInto(out *TLSSpec) {
*out = *in

View File

@@ -1,8 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by deepcopy-gen. DO NOT EDIT.
package authentication

View File

@@ -1,4 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config

View File

@@ -1,12 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

View File

@@ -3,7 +3,6 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/generated/1.17/apis/concierge/config
// +k8s:defaulter-gen=TypeMeta
// +groupName=config.concierge.pinniped.dev

View File

@@ -24,7 +24,7 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.

View File

@@ -67,19 +67,21 @@ type CredentialIssuerStrategy struct {
// Describes the configuration status of a Pinniped credential issuer.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped,scope=Cluster
// +kubebuilder:subresource:status
type CredentialIssuer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Status of the credential issuer.
// +optional
Status CredentialIssuerStatus `json:"status"`
}
// List of CredentialIssuer objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CredentialIssuerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

View File

@@ -1,22 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
return nil
}

View File

@@ -1,19 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@@ -1,8 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by deepcopy-gen. DO NOT EDIT.
package config

View File

@@ -27,7 +27,6 @@ type TokenCredentialRequestStatus struct {
}
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TokenCredentialRequest struct {
metav1.TypeMeta

View File

@@ -30,6 +30,7 @@ type TokenCredentialRequestStatus struct {
// TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
// +genclient
// +genclient:nonNamespaced
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type TokenCredentialRequest struct {
metav1.TypeMeta `json:",inline"`

View File

@@ -1,8 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=config.supervisor.pinniped.dev
// Package config is the internal version of the Pinniped configuration API.
package config

View File

@@ -1,4 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package config

View File

@@ -1,4 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1

View File

@@ -1,12 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) error {
return RegisterDefaults(scheme)
}

View File

@@ -24,14 +24,14 @@ func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
localSchemeBuilder.Register(addKnownTypes)
}
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&OIDCProvider{},
&OIDCProviderList{},
&FederationDomain{},
&FederationDomainList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -8,20 +8,20 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid
type OIDCProviderStatusCondition string
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
const (
SuccessOIDCProviderStatusCondition = OIDCProviderStatusCondition("Success")
DuplicateOIDCProviderStatusCondition = OIDCProviderStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretOIDCProviderStatusCondition = OIDCProviderStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidOIDCProviderStatusCondition = OIDCProviderStatusCondition("Invalid")
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
)
// OIDCProviderTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
type OIDCProviderTLSSpec struct {
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
type FederationDomainTLSSpec struct {
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
// the TLS serving certificate for the HTTPS endpoints served by this OIDCProvider. When provided, the TLS Secret
// the TLS serving certificate for the HTTPS endpoints served by this FederationDomain. When provided, the TLS Secret
// named here must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use
// for TLS.
//
@@ -41,8 +41,8 @@ type OIDCProviderTLSSpec struct {
SecretName string `json:"secretName,omitempty"`
}
// OIDCProviderSpec is a struct that describes an OIDC Provider.
type OIDCProviderSpec struct {
// FederationDomainSpec is a struct that describes an OIDC Provider.
type FederationDomainSpec struct {
// Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the
// identifier that it will use for the iss claim in issued JWTs. This field will also be used as
// the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is
@@ -54,17 +54,41 @@ type OIDCProviderSpec struct {
// +kubebuilder:validation:MinLength=1
Issuer string `json:"issuer"`
// TLS configures how this OIDCProvider is served over Transport Layer Security (TLS).
// TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
// +optional
TLS *OIDCProviderTLSSpec `json:"tls,omitempty"`
TLS *FederationDomainTLSSpec `json:"tls,omitempty"`
}
// OIDCProviderStatus is a struct that describes the actual state of an OIDC Provider.
type OIDCProviderStatus struct {
// FederationDomainSecrets holds information about this OIDC Provider's secrets.
type FederationDomainSecrets struct {
// JWKS holds the name of the corev1.Secret in which this OIDC Provider's signing/verification keys are
// stored. If it is empty, then the signing/verification keys are either unknown or they don't
// exist.
// +optional
JWKS corev1.LocalObjectReference `json:"jwks,omitempty"`
// TokenSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// signing tokens is stored.
// +optional
TokenSigningKey corev1.LocalObjectReference `json:"tokenSigningKey,omitempty"`
// StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// signing state parameters is stored.
// +optional
StateSigningKey corev1.LocalObjectReference `json:"stateSigningKey,omitempty"`
// StateSigningKey holds the name of the corev1.Secret in which this OIDC Provider's key for
// encrypting state parameters is stored.
// +optional
StateEncryptionKey corev1.LocalObjectReference `json:"stateEncryptionKey,omitempty"`
}
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status OIDCProviderStatusCondition `json:"status,omitempty"`
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Message provides human-readable details about the Status.
// +optional
@@ -76,32 +100,32 @@ type OIDCProviderStatus struct {
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// JWKSSecret holds the name of the secret in which this OIDC Provider's signing/verification keys
// are stored. If it is empty, then the signing/verification keys are either unknown or they don't
// exist.
// Secrets contains information about this OIDC Provider's secrets.
// +optional
JWKSSecret corev1.LocalObjectReference `json:"jwksSecret,omitempty"`
Secrets FederationDomainSecrets `json:"secrets,omitempty"`
}
// OIDCProvider describes the configuration of an OIDC provider.
// FederationDomain describes the configuration of an OIDC provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OIDCProvider struct {
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:subresource:status
type FederationDomain struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec of the OIDC provider.
Spec OIDCProviderSpec `json:"spec"`
Spec FederationDomainSpec `json:"spec"`
// Status of the OIDC provider.
Status OIDCProviderStatus `json:"status,omitempty"`
Status FederationDomainStatus `json:"status,omitempty"`
}
// List of OIDCProvider objects.
// List of FederationDomain objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type OIDCProviderList struct {
type FederationDomainList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []OIDCProvider `json:"items"`
Items []FederationDomain `json:"items"`
}

View File

@@ -1,22 +0,0 @@
// +build !ignore_autogenerated
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
return nil
}

View File

@@ -12,7 +12,7 @@ import (
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCProvider) DeepCopyInto(out *OIDCProvider) {
func (in *FederationDomain) DeepCopyInto(out *FederationDomain) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
@@ -21,18 +21,18 @@ func (in *OIDCProvider) DeepCopyInto(out *OIDCProvider) {
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProvider.
func (in *OIDCProvider) DeepCopy() *OIDCProvider {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomain.
func (in *FederationDomain) DeepCopy() *FederationDomain {
if in == nil {
return nil
}
out := new(OIDCProvider)
out := new(FederationDomain)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *OIDCProvider) DeepCopyObject() runtime.Object {
func (in *FederationDomain) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -40,13 +40,13 @@ func (in *OIDCProvider) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCProviderList) DeepCopyInto(out *OIDCProviderList) {
func (in *FederationDomainList) DeepCopyInto(out *FederationDomainList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]OIDCProvider, len(*in))
*out = make([]FederationDomain, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -54,18 +54,18 @@ func (in *OIDCProviderList) DeepCopyInto(out *OIDCProviderList) {
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderList.
func (in *OIDCProviderList) DeepCopy() *OIDCProviderList {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainList.
func (in *FederationDomainList) DeepCopy() *FederationDomainList {
if in == nil {
return nil
}
out := new(OIDCProviderList)
out := new(FederationDomainList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *OIDCProviderList) DeepCopyObject() runtime.Object {
func (in *FederationDomainList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -73,59 +73,79 @@ func (in *OIDCProviderList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCProviderSpec) DeepCopyInto(out *OIDCProviderSpec) {
func (in *FederationDomainSecrets) DeepCopyInto(out *FederationDomainSecrets) {
*out = *in
out.JWKS = in.JWKS
out.TokenSigningKey = in.TokenSigningKey
out.StateSigningKey = in.StateSigningKey
out.StateEncryptionKey = in.StateEncryptionKey
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainSecrets.
func (in *FederationDomainSecrets) DeepCopy() *FederationDomainSecrets {
if in == nil {
return nil
}
out := new(FederationDomainSecrets)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainSpec) DeepCopyInto(out *FederationDomainSpec) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(OIDCProviderTLSSpec)
*out = new(FederationDomainTLSSpec)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderSpec.
func (in *OIDCProviderSpec) DeepCopy() *OIDCProviderSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainSpec.
func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
if in == nil {
return nil
}
out := new(OIDCProviderSpec)
out := new(FederationDomainSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCProviderStatus) DeepCopyInto(out *OIDCProviderStatus) {
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
}
out.JWKSSecret = in.JWKSSecret
out.Secrets = in.Secrets
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderStatus.
func (in *OIDCProviderStatus) DeepCopy() *OIDCProviderStatus {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainStatus.
func (in *FederationDomainStatus) DeepCopy() *FederationDomainStatus {
if in == nil {
return nil
}
out := new(OIDCProviderStatus)
out := new(FederationDomainStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OIDCProviderTLSSpec) DeepCopyInto(out *OIDCProviderTLSSpec) {
func (in *FederationDomainTLSSpec) DeepCopyInto(out *FederationDomainTLSSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCProviderTLSSpec.
func (in *OIDCProviderTLSSpec) DeepCopy() *OIDCProviderTLSSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FederationDomainTLSSpec.
func (in *FederationDomainTLSSpec) DeepCopy() *FederationDomainTLSSpec {
if in == nil {
return nil
}
out := new(OIDCProviderTLSSpec)
out := new(FederationDomainTLSSpec)
in.DeepCopyInto(out)
return out
}

Some files were not shown because too many files have changed in this diff Show More