Compare commits

..

714 Commits

Author SHA1 Message Date
Ryan Richard
b77297c68d Validate the upstream email_verified claim when it makes sense 2021-01-25 15:10:41 -08: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
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
Ryan Richard
1223cf7877 Merge pull request #154 from vmware-tanzu/change_release_static_yaml_names
Rename static yaml files in release process
2020-11-02 17:09:11 -08:00
Ryan Richard
036845deee Merge pull request #184 from vmware-tanzu/bump_golang_and_slim
Upgrade golang patch release to 1.15.3 and debian 10.5-slim -> 10.6-slim
2020-11-02 17:08:48 -08:00
Matt Moyer
c451604816 Merge pull request #182 from mattmoyer/more-renames
Rename more APIs before we cut a release with longer-term API compatibility
2020-11-02 18:34:26 -06:00
Ryan Richard
05cf56a0fa Merge pull request #180 from vmware-tanzu/limits
Add CPU/memory limits to our deployments
2020-11-02 16:22:37 -08:00
Ryan Richard
5a0e7fd358 Upgrade golang patch release to 1.15.3 and debian 10.5-slim -> 10.6-slim 2020-11-02 16:17:15 -08:00
Matt Moyer
2bf5c8b48b Replace the OIDCProvider field SNICertificateSecretName with a TLS.SecretName field.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-02 18:15:03 -06:00
Ryan Richard
05233963fb Add CPU requests and limits to the Concierge and Supervisor deployments 2020-11-02 15:47:20 -08:00
Matt Moyer
2b8773aa54 Rename OIDCProviderConfig to OIDCProvider.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-02 17:40:39 -06:00
Matt Moyer
59263ea733 Rename CredentialIssuerConfig to CredentialIssuer.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-02 17:39:42 -06:00
Matt Moyer
b13a8075e4 Merge pull request #183 from vmware-tanzu/non-root
Run as non-root
2020-11-02 17:39:14 -06:00
Ryan Richard
d596f8c3e5 Empty commit to trigger CI 2020-11-02 15:18:39 -08:00
Ryan Richard
75c35e74cc Refactor and add unit tests for previous commit to run agent pod as root 2020-11-02 15:03:37 -08:00
Matt Moyer
e4f4cd7ca0 Merge pull request #181 from mattmoyer/add-psp-cluster-role-permission
Give the concierge access to use any PodSecurityPolicy.
2020-11-02 15:35:56 -06:00
Ryan Richard
a01921012d kubecertagent: explicitly run as root
We need root here because the files that this pod reads are
most likely restricted to root access.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-02 16:33:46 -05:00
Ryan Richard
2e50e8f01b hack/lib/tilt: run Tilt images with non-root user
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-02 16:32:50 -05:00
Matt Moyer
935577f8e7 Give the concierge access to use any PodSecurityPolicy.
This is needed on clusters with PodSecurityPolicy enabled by default, but should be harmless in other cases.

This is generally needed because a restrictive PodSecurityPolicy will usually otherwise prevent the `hostPath` volume mount needed by the dynamically-created cert agent pod.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-11-02 15:10:00 -06:00
Ryan Richard
781f86d18c deploy: add memory limits
This is the beginning of a change to add cpu/memory limits to our pods.
We are doing this because some consumers require this, and it is generally
a good practice.

The limits == requests for "Guaranteed" QoS.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-02 14:57:39 -05:00
Andrew Keesler
fcea48c8f9 Run as non-root
I tried to follow a principle of encapsulation here - we can still default to
peeps making connections to 80/443 on a Service object, but internally we will
use 8080/8443.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-02 12:51:15 -05:00
Andrew Keesler
7639d5e161 Merge pull request #178 from ankeesler/test-cleanup
test/integration: protect from NPE and follow doc conventions
2020-11-02 12:22:34 -05:00
Ryan Richard
ab5c04b1f3 Merge pull request #176 from vmware-tanzu/agent_pod_additional_label_handling
Handle custom labels better in the agent pod controllers
2020-11-02 09:08:42 -08:00
Andrew Keesler
fb3c5749e8 test/integration: protect from NPE and follow doc conventions
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-11-02 11:51:02 -05:00
Ryan Richard
7597b12a51 Small unit test changes for deleter_test.go 2020-11-02 08:40:39 -08:00
Matt Moyer
5bbfc35d27 Merge pull request #175 from mattmoyer/split-config-apis
Split the config CRDs into two API groups.
2020-10-30 19:42:03 -05:00
Ryan Richard
f76b9857da Don't use custom labels when selecting an agent pod
And delete the agent pod when it needs its custom labels to be
updated, so that the creator controller will notice that it is missing
and immediately create it with the new custom labels.
2020-10-30 17:41:17 -07:00
Matt Moyer
9e1922f1ed Split the config CRDs into two API groups.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 19:22:46 -05:00
Ryan Richard
01f4fdb5c3 Remove namespace from a ClusterRoleBinding, which are not namespaced 2020-10-30 16:10:04 -07:00
Andrew Keesler
a5379c08e2 Whitespace-only change in two files
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-30 15:18:40 -07:00
Matt Moyer
ad95bb44b0 Merge pull request #174 from mattmoyer/rename-webhook-idp
Rename webhook configuration CRD "WebhookAuthenticator" in group "authentication.concierge.pinniped.dev".
2020-10-30 15:50:39 -05:00
Ryan Richard
4b7592feaf Skip a part of an integration test which is not so easy with real Ingress
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-30 13:19:34 -07:00
Matt Moyer
34da8c7877 Rename existing references to "IDP" and "Identity Provider".
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 15:12:01 -05:00
Matt Moyer
f3a83882a4 Rename the IdentityProvider field to Authenticator in TokenCredentialRequest.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 15:11:53 -05:00
Matt Moyer
0f25657a35 Rename WebhookIdentityProvider to WebhookAuthenticator.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 15:11:53 -05:00
Matt Moyer
e69183aa8a Rename idp.concierge.pinniped.dev to authentication.concierge.pinniped.dev.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 14:07:40 -05:00
Matt Moyer
81390bba89 Rename idp.pinniped.dev to idp.concierge.pinniped.dev.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 14:07:39 -05:00
Matt Moyer
59431a3d3d Merge pull request #173 from mattmoyer/parallel-codegen
Do codegen across all version in parallel.
2020-10-30 13:45:21 -05:00
Matt Moyer
9760c03617 Do codegen across all version in parallel.
This only matters for local development, since we don't use this script directly in CI. Makes the full codegen ste take ~90s on my laptop.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 11:12:53 -05:00
Matt Moyer
8b8ffc21c4 Merge pull request #172 from mattmoyer/rename-login-api
Rename login API to `login.concierge.pinniped.dev`.
2020-10-30 10:23:45 -05:00
Matt Moyer
f0320dfbd8 Rename login API to login.concierge.pinniped.dev.
This is the first of a few related changes that re-organize our API after the big recent changes that introduced the supervisor component.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-30 09:58:28 -05:00
Ryan Richard
3277e778ea Add a comment to an integration test 2020-10-29 15:42:22 -07:00
Ryan Richard
9c13b7144e Merge pull request #170 from vmware-tanzu/oidc_https_endpoints
Add HTTPS endpoints for OIDC providers, and terminate TLS with the configured certificates
2020-10-28 17:15:11 -07:00
Ryan Richard
059b6e885f Allow ytt templating of the loadBalancerIP for the supervisor 2020-10-28 16:45:23 -07:00
Ryan Richard
4af508981a Make default TLS secret name from app name in supervisor_discovery_test.go 2020-10-28 16:11:19 -07:00
Ryan Richard
a007fc3bd3 Form paths correctly when the path arg is empty in supervisor_discovery_test.go 2020-10-28 15:22:53 -07:00
Ryan Richard
c52874250a Fix a mistake in supervisor_discovery_test.go
- Should not fail when the default TLS cert does not exist in the
  test cluster before the test started
2020-10-28 14:25:01 -07:00
Ryan Richard
01dddd3cae Add some docs for configuring supervisor TLS
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-28 13:42:02 -07:00
Andrew Keesler
bd04570e51 supervisor_discovery_test.go tests hostnames are treated as case-insensitive
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-28 13:09:20 -07:00
Ryan Richard
8ff64d4c1a Require https scheme for OIDCProviderConfig Issuer field
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-28 12:49:41 -07:00
Andrew Keesler
2542a8e175 Stash and restore any pre-existing default TLS cert in supervisor_discovery_test.go
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-28 12:32:21 -07:00
Ryan Richard
29e0ce5662 Configure name of the supervisor default TLS cert secret via ConfigMap
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-28 11:56:50 -07:00
Ryan Richard
978ecda758 Test SNI & default certs being used at the same time in integration test 2020-10-28 08:58:50 -07:00
Ryan Richard
170d3a3993 Forgot to commit some test fixtures in a prior commit 2020-10-27 17:00:00 -07:00
Ryan Richard
2777c4e9f3 Update prepare-for-integration-tests.sh to use ./hack/kind-{up,down}.sh 2020-10-27 16:56:53 -07:00
Ryan Richard
38802c2184 Add a way to set a default supervisor TLS cert for when SNI won't work
- Setting a Secret in the supervisor's namespace with a special name
  will cause it to get picked up and served as the supervisor's TLS
  cert for any request which does not have a matching SNI cert.
- This is especially useful for when there is no DNS record for an
  issuer and the user will be accessing it via IP address. This
  is not how we would expect it to be used in production, but it
  might be useful for other cases.
- Includes a new integration test
- Also suppress all of the warnings about ignoring the error returned by
  Close() in lines like `defer x.Close()` to make GoLand happier
2020-10-27 16:33:08 -07:00
Andrew Keesler
7bce16737b Get rid of WIP workflow
See d5dd65c, 45189e3, 96c4661. I pushed to the wrong remote...

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-27 18:39:19 -04:00
Andrew Keesler
96c4661a25 Fix unit-tests workflow YAML.
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-27 18:26:11 -04:00
Andrew Keesler
45189e3e2b No way this windows-unit-tests workflow works.
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-27 18:20:12 -04:00
Andrew Keesler
d5dd65cfe8 So...does this macos-unit-tests workflow work?
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-27 18:00:54 -04:00
Ryan Richard
1f1b6c884e Add integration test: supervisor TLS termination and SNI virtual hosting
- Also reduce the minimum allowed TLS version to v1.2, because v1.3
  is not yet supported by some common clients, e.g. the default MacOS
  curl command
2020-10-27 14:57:25 -07:00
Ryan Richard
eeb110761e Rename secretName to SNICertificateSecretName in OIDCProviderConfig 2020-10-26 17:25:45 -07:00
Ryan Richard
8b7c30cfbd Supervisor listens for HTTPS on port 443 with configurable TLS certs
- TLS certificates can be configured on the OIDCProviderConfig using
  the `secretName` field.
- When listening for incoming TLS connections, choose the TLS cert
  based on the SNI hostname of the incoming request.
- Because SNI hostname information on incoming requests does not include
  the port number of the request, we add a validation that
  OIDCProviderConfigs where the issuer hostnames (not including port
  number) are the same must use the same `secretName`.
- Note that this approach does not yet support requests made to an
  IP address instead of a hostname. Also note that `localhost` is
  considered a hostname by SNI.
- Add port 443 as a container port to the pod spec.
- A new controller watches for TLS secrets and caches them in memory.
  That same in-memory cache is used while servicing incoming connections
  on the TLS port.
- Make it easy to configure both port 443 and/or port 80 for various
  Service types using our ytt templates for the supervisor.
- When deploying to kind, add another nodeport and forward it to the
  host on another port to expose our new HTTPS supervisor port to the
  host.
2020-10-26 17:03:26 -07:00
Matt Moyer
7880f7ea41 Merge pull request #171 from danjahner/main
Rename logo file
2020-10-26 17:20:36 -05:00
Dan Jahner
13ccb07fe4 Rename logo file 2020-10-26 15:06:04 -07:00
Matt Moyer
6c092deba5 Merge pull request #169 from mattmoyer/promote-login-command
Promote the `pinniped login` command out of alpha.
2020-10-23 19:48:44 -05:00
Ryan Richard
25a91019c2 Add spec.secretName to OPC and handle case-insensitive hostnames
- When two different Issuers have the same host (i.e. they differ
  only by path) then they must have the same secretName. This is because
  it wouldn't make sense for there to be two different TLS certificates
  for one host. Find any that do not have the same secret name to
  put an error status on them and to avoid serving OIDC endpoints for
  them. The host comparison is case-insensitive.
- Issuer hostnames should be treated as case-insensitive, because
  DNS hostnames are case-insensitive. So https://me.com and
  https://mE.cOm are duplicate issuers. However, paths are
  case-sensitive, so https://me.com/A and https://me.com/a are
  different issuers. Fixed this in the issuer validations and in the
  OIDC Manager's request router logic.
2020-10-23 16:25:44 -07:00
Matt Moyer
7615667b9b Update TestCLILoginOIDC to use new non-alpha login command.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-23 14:44:42 -05:00
Matt Moyer
0948457521 Promote the pinniped login command out of alpha.
This was hidden behind a `pinniped alpha` hidden subcommand, but we're comfortable enough with the CLI flag interface now to promote it.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-23 14:44:41 -05:00
Andrew Keesler
110c72a5d4 dynamiccertauthority: fix cert expiration test failure
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-23 15:34:25 -04:00
Andrew Keesler
f928ef4752 Also mention using a service mesh is an option for supervisor ingress
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-23 10:23:17 -07:00
Ryan Richard
eafdef7b11 Add docs for creating an Ingress for the Supervisor
Note that some of these new docs mention things that will not be
implemented until we finish the next story.
2020-10-22 16:57:50 -07:00
Matt Moyer
4c844ba334 Merge pull request #168 from mattmoyer/cli-session-refresh
Add support for refresh token flow in OIDC CLI client.
2020-10-22 18:13:42 -05:00
Matt Moyer
07001e5ee3 Extend TestCLILoginOIDC to test refresh flow.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-22 17:54:31 -05:00
Matt Moyer
3508a28369 Implement refresh flow in ./internal/oidcclient package.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-22 17:54:31 -05:00
Ryan Richard
397ec61e57 Specify the supervisor NodePort Service's port and nodePort separately
When using kind we forward the node's port to the host, so we only
really care about the `nodePort` value. For acceptance clusters,
we put an Ingress in front of a NodePort Service, so we only really
care about the `port` value.
2020-10-22 15:37:35 -07:00
Ryan Richard
8ae04605ca Add comments for magic 31234 port
Also delete hack/lib/kind-config/multi-node.yaml since we don't think we will
use it...

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-22 17:54:35 -04:00
Matt Moyer
8772a00824 Merge pull request #167 from mattmoyer/fix-accidental-timeout-regression
Fix a timeout in TestCLILoginOIDC that was accidentally shortened.
2020-10-22 12:24:49 -05:00
Matt Moyer
ce598eb58e Fix a timeout in TestCLILoginOIDC that was accidentally shortened in 0adbb5234e.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-22 11:49:04 -05:00
Matt Moyer
4b24e9c625 Merge pull request #166 from mattmoyer/add-cli-test-debug-output
Add some verbose logging to TestCLILoginOIDC.
2020-10-22 11:17:18 -05:00
Matt Moyer
fe3b44b134 Add some verbose logging to TestCLILoginOIDC.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-22 10:33:37 -05:00
Ryan Richard
122f7cffdb Make the supervisor healthz endpoint public
Based on our experiences today with GKE, it will be easier for our users
to configure Ingress health checks if the healthz endpoint is available
on the same public port as the OIDC endpoints.

Also add an integration test for the healthz endpoint now that it is
public.

Also add the optional `containers[].ports.containerPort` to the
supervisor Deployment because the GKE docs say that GKE will look
at that field while inferring how to invoke the health endpoint. See
https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#def_inf_hc
2020-10-21 15:24:58 -07:00
Matt Moyer
5dbc03efe9 Merge pull request #165 from mattmoyer/cli-session-cache
Add basic file-based session cache for CLI OIDC client.
2020-10-21 16:30:03 -05:00
Matt Moyer
0adbb5234e Extend TestCLILoginOIDC to test ID token caching behavior.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-21 15:02:42 -05:00
Matt Moyer
e919ef6582 Add a file-based session cache.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-21 14:28:05 -05:00
Andrew Keesler
fa5f653de6 Implement readinessProbe and livenessProbe for supervisor
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-21 11:51:31 -07:00
Matt Moyer
e8113e3770 Add basic caching framework to ./internal/oidclient package.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-21 13:14:16 -05:00
Matt Moyer
7f6a82aa91 Refactor and rename ./internal/oidcclient/login to ./internal/oidcclient. 2020-10-21 13:07:21 -05:00
Matt Moyer
4ef41f969d Add a util helper for marking a CLI flag as hidden.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-21 13:07:21 -05:00
Andrew Keesler
3e39800005 Merge pull request #164 from vmware-tanzu/virtual-hosts
Virtual hosts integration test
2020-10-21 09:16:59 -04:00
Ryan Richard
52ebd77527 Add optional PINNIPED_TEST_SUPERVISOR_HTTPS_CA_BUNDLE for integration tests
- Not used by any of our integration test clusters yet
- Planning to use it later for the kind clusters and maybe for
  the acceptance clusters too (although the acceptance clusters might
  not need to use self-signed certs so maybe not)
2020-10-20 16:46:33 -07:00
Ryan Richard
ec21fc8595 Also delete the final OIDCProviderConfig made by an integration test
- It didn't matter before because it would be cleaned up by a
  t.Cleanup() function, but now that we might loop twice it will matter
  during the second time through the loop
2020-10-20 15:59:25 -07:00
Ryan Richard
276dff5772 Introduce PINNIPED_TEST_SUPERVISOR_HTTPS_ADDRESS
- We plan to use this on acceptance clusters
- We also plan to use this for a future story in the kind-based tests,
  but not yet
2020-10-20 15:57:10 -07:00
Ryan Richard
90235418b9 Add a test for when issuer hostname and supervisor public address differ 2020-10-20 15:22:03 -07:00
Ryan Richard
9ba93d66c3 test/integration: prefactoring for testing virtual hosts
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-20 17:00:36 -04:00
Ryan Richard
aff85acf37 Merge pull request #163 from vmware-tanzu/discovery_jwks
Implement per-issuer OIDC JWKS endpoint
2020-10-19 13:00:49 -07:00
Ryan Richard
4da64f38b5 Integration test for per-issuer OIDC JWKS endpoints 2020-10-19 12:21:18 -07:00
Ryan Richard
d9d76726c2 Implement per-issuer OIDC JWKS endpoint 2020-10-16 17:51:40 -07:00
Ryan Richard
08659a6583 Merge pull request #158 from vmware-tanzu/label_every_resource
Custom labels can to be applied to all k8s resources created by Pinniped
2020-10-15 14:02:29 -07:00
Andrew Keesler
e2630be00a Update feature proposal template to work for users and contributors 2020-10-15 17:01:24 -04:00
Andrew Keesler
8fe031e73d Do not copy pkg directory in Dockerfile
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-15 13:31:16 -07:00
Andrew Keesler
617c5608ca Supervisor controllers apply custom labels to JWKS secrets
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-15 12:40:56 -07:00
Andrew Keesler
dda3c21a8e Add missing parenthesis to bug report template
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 14:07:43 -04:00
Ryan Richard
f8e461dfc3 Merge branch 'main' into label_every_resource 2020-10-15 10:19:03 -07:00
Ryan Richard
94f20e57b1 Concierge controllers add labels to all created resources 2020-10-15 10:14:23 -07:00
Andrew Keesler
943286bbc6 Merge pull request #157 from ankeesler/generate-jwk-key
Pinniped federation server generates and persists a JWT signing key
2020-10-15 11:55:22 -04:00
Andrew Keesler
e05213f9dd supervisor-generate-key: use EC keys intead of RSA
EC keys are smaller and take less time to generate. Our integration
tests were super flakey because generating an RSA key would take up to
10 seconds *gasp*. The main token verifier that we care about is
Kubernetes, which supports P256, so hopefully it won't be that much of
an issue that our default signing key type is EC. The OIDC spec seems
kinda squirmy when it comes to using non-RSA signing algorithms...

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 11:33:08 -04:00
Andrew Keesler
5a0dab768f test/integration: remove unused function (see 31225ac7a)
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 09:26:15 -04:00
Andrew Keesler
fbcce700dc Fix whitespace/spelling nits in JWKS controller
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 09:22:17 -04:00
Andrew Keesler
a5abe9ca3e hack/lib/tilt: fix deployment change leftover from c030551a
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 09:20:09 -04:00
Andrew Keesler
1b99983441 apis: fix indentation in Go type
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 09:19:00 -04:00
Andrew Keesler
31225ac7ae test/integration: reuse CreateTestOIDCProvider helper
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 09:09:49 -04:00
Andrew Keesler
f21122a309 Merge remote-tracking branch 'upstream/main' into generate-jwk-key 2020-10-15 07:51:15 -04:00
Andrew Keesler
aef25163e2 Get rid of an extra dependency from c030551
I brought this over because I copied code from work in flight on
another branch. But now the other branch doesn't use this package.
No use bringing on another dependency if we can avoid it.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-15 07:51:07 -04:00
Andrew Keesler
87c7e9a556 hack/prepare-for-integration-tests.sh: default COLORTERM for when not set
Signed-off-by: Andrew Keesler <ankeesler1@gmail.com>
2020-10-14 20:18:10 -04:00
Ryan Richard
c05bdb58ac Merge branch 'main' into label_every_resource 2020-10-14 16:24:51 -07:00
Ryan Richard
84a0084703 Tilefile watches for changes in ytt templates
- When using `local()` in the Tiltfile it will not know
  to watch those files for changes, so each time we use
  `local()` we now also use `watch_file()`
- As a result, editing a ytt template file now causes
  an immediate `kubectl apply` of the results
2020-10-14 16:21:40 -07:00
Ryan Richard
1301018655 Support installing concierge and supervisor into existing namespace
- New optional ytt value called `into_namespace` means install into that
  preexisting namespace rather than creating a new namespace for each app
- Also ensure that every resource that is created statically by our yaml
  at install-time by either app is labeled consistently
- Also support adding custom labels to all of those resources from a
  new ytt value called `custom_labels`
2020-10-14 15:05:42 -07:00
Andrew Keesler
76e89b523b Merge remote-tracking branch 'upstream/main' into generate-jwk-key 2020-10-14 17:40:17 -04:00
Andrew Keesler
c030551af0 supervisor-generate-key: unit and integration tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-14 16:41:16 -04:00
Matt Moyer
cd970616da Merge pull request #149 from mattmoyer/oidc-cli-part-2
Finish initial OIDC CLI client implementation.
2020-10-14 13:40:12 -05:00
Matt Moyer
68d20298f2 Fix chromedriver usage inside our test container.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-14 13:18:11 -05:00
Matt Moyer
19a1d569c9 Restructure this test to avoid data races.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-14 12:28:08 -05:00
Ryan Richard
a197a26335 Change community meeting time
And some other general cleanup
2020-10-14 09:54:09 -07:00
Andrew Keesler
6aed025c79 supervisor-generate-key: initial spike
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-14 09:47:34 -04:00
Andrew Keesler
aa705afc72 hack/tilt-up.sh: let folks specify tilt flags
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-14 09:22:21 -04:00
Andrew Keesler
3d5937a8e8 deploy/supervisor: type: eaxmple -> example
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-14 09:22:15 -04:00
Matt Moyer
33fcc74417 Add Dex to our integration test environment and use it to test the CLI.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-13 16:50:38 -05:00
Matt Moyer
50d80489be Add initial CLI integration test for OIDC login.
This is our first test using a real browser to interact with an upstream provider.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-13 10:41:53 -05:00
Matt Moyer
8a16a92c01 Rename some existing CLI test code.
It will no longer be the only CLI test, so the names should be a bit more specific.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-13 10:25:39 -05:00
Matt Moyer
d1e86e2616 Rename "TestClusterCapability" to more generic "Capability."
This will be used for other types of "capabilities" of the test environment besides just those of the test cluster, such as those of an upstream OIDC provider.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-13 09:13:40 -05:00
Matt Moyer
67b692b11f Implement the rest of an OIDC client CLI library.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-12 16:41:46 -05:00
Matt Moyer
ce49d8bd7b Remove the --use-pkce flag and just always use it.
Based on the spec, it seems like it's required that OAuth2 servers which do not support PKCE should just ignore the parameters, so this should always work.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-12 16:41:46 -05:00
Matt Moyer
a13d7ec5a1 Remove temporary --debug-auth-code-exchange flag for OIDC client CLI.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-12 16:41:46 -05:00
Ryan Richard
478b0a0fd8 Add supervisor yaml and rename concierge yaml in release process
Add install-pinniped-supervisor.yaml and rename install-pinniped.yaml
to install-pinniped-concierge.yaml in the release process and
installation/demo documentation.
2020-10-12 09:43:52 -07:00
Ryan Richard
ff545db869 Merge pull request #148 from vmware-tanzu/supervisor-with-discovery
Beginning of a Pinniped Supervisor Server, starting with an OIDC Discovery Endpoint
2020-10-09 18:58:15 -07:00
Ryan Richard
6b135b93cf Binding both kind workers to the same localhost port fails, so just bind one 2020-10-09 18:42:15 -07:00
Ryan Richard
d81d395c80 Get ready to deploy Supervisor in CI and run its integration tests
- Also use ./test/integration instead of ./test/... everywhere because
  it will stream the output of the tests while they run
2020-10-09 18:07:13 -07:00
Ryan Richard
171f3ed906 Add some docs for how to configure the Supervisor app after installing 2020-10-09 16:28:34 -07:00
Ryan Richard
354b922e48 Allow creation of different Service types in Supervisor ytt templates
- Tiltfile and prepare-for-integration-tests.sh both specify the
  NodePort Service using `--data-value-yaml 'service_nodeport_port=31234'`
- Also rename the namespaces used by the Concierge and Supervisor apps
  during integration tests running locally
2020-10-09 16:00:11 -07:00
Ryan Richard
34549b779b Make tilt work with the supervisor app and add more uninstall testing
- Also continue renaming things related to the concierge app
- Enhance the uninstall test to also test uninstalling the supervisor
  and local-user-authenticator apps
2020-10-09 14:25:34 -07:00
Ryan Richard
72b2d02777 Rename integration test env variables
- Variables specific to concierge add it to their name
- All variables now start with `PINNIPED_TEST_` which makes it clear
  that they are for tests and also helps them not conflict with the
  env vars that are used in the Pinniped CLI code
2020-10-09 10:11:47 -07:00
Ryan Richard
b71959961d Merge branch 'main' into supervisor-with-discovery 2020-10-09 10:00:50 -07:00
Ryan Richard
f5a6a0bb1e Move all three deployment dirs under a new top-level deploy/ dir 2020-10-09 10:00:22 -07:00
Andrew Keesler
c555c14ccb supervisor-oidc: add OIDCProviderConfig.Status.LastUpdateTime
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-09 11:54:50 -04:00
Andrew Keesler
bb015adf4e Backfill tests to OIDCProviderConfig controller
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-09 10:39:17 -04:00
Andrew Keesler
fac4d074d0 internal/multierror: add tests
Signed-off-by: Andrew Keesler <ankeesler1@gmail.com>
2020-10-09 08:00:41 -04:00
Ryan Richard
b74486f305 Start back-filling unit tests for OIDCProviderConfigWatcherController
- Left some TODOs for more things that it should test
2020-10-08 17:40:58 -07:00
Ryan Richard
a4389562e3 Fix mistake in deployment.yaml where service selector was hardcoded 2020-10-08 16:20:21 -07:00
Andrew Keesler
05141592f8 Refactor provider.Manager
- And also handle when an issuer's path is a subpath of another issuer

Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-08 14:40:56 -07:00
Ryan Richard
8b7d96f42c Several small refactors related to OIDC providers 2020-10-08 11:28:21 -07:00
Andrew Keesler
da00fc708f supervisor-oidc: checkpoint: add status to provider CRD
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-08 13:27:45 -04:00
Ryan Richard
6b653fc663 Creation and deletion of OIDC Provider discovery endpoints from config
- The OIDCProviderConfigWatcherController synchronizes the
  OIDCProviderConfig settings to dynamically mount and unmount the
  OIDC discovery endpoints for each provider
- Integration test passes but unit tests need to be added still
2020-10-07 19:18:34 -07:00
Andrew Keesler
154de991e4 Make concierge_api_discovery_test.go less sensitive to order in a list
Signed-off-by: Ryan Richard <richardry@vmware.com>
2020-10-07 11:42:30 -07:00
Andrew Keesler
f48a4e445e Fix linting and unit tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-07 11:48:21 -04:00
Andrew Keesler
20ce142f90 Merge remote-tracking branch 'upstream/main' into supervisor-with-discovery 2020-10-07 11:37:33 -04:00
Andrew Keesler
c49ebf4b57 supervisor-oidc: int test passes, but impl needs refactor
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-07 11:33:50 -04:00
Andrew Keesler
019f44982c supervisor-oidc: checkpoint: controller watches OIDCProviderConfig
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-07 10:54:56 -04:00
Andrew Keesler
8a772793b8 supervisor-oidc: fix PINNIPED_SUPERVISOR test env vars?
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-07 10:51:39 -04:00
Andrew Keesler
ead1ade24b supervisor-oidc: forgot OIDCProviderConfig type registration in 14f1d86
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-07 10:50:55 -04:00
Ryan Richard
ae56fcb46a Add integration test for the OIDC discovery endpoint
- Intended to be a red test in this commit; will make it go
  green in a future commit
- Enhance env.go and prepare-for-integration-tests.sh to make it
  possible to write integration tests for the supervisor app
  by setting more env vars and by exposing the service to the kind
  host on a localhost port
- Add `--clean` option to prepare-for-integration-tests.sh
  to make it easier to start fresh
- Make prepare-for-integration-tests.sh advise you to run
  `go test -v -count 1 ./test/integration` because this does
  not buffer the test output
- Make concierge_api_discovery_test.go pass by adding expectations
  for the new OIDCProviderConfig type
2020-10-06 17:53:29 -07:00
Ryan Richard
a7c334a0f3 Update the file used as the demo screencast
New version of the file was created by @danjahner
2020-10-06 17:11:08 -07:00
Ryan Richard
044b5c4d46 Merge pull request #151 from vmware-tanzu/demo-screencast
Add demo screencast and do some cleanup in demo.md
2020-10-06 17:07:27 -07:00
Ryan Richard
6f8f99e49b Add demo screencast and do some cleanup in demo.md
- Note that this avoids committing the demo screencast
  file to our git history because it is 5.76 MB. We won't
  want to need to download that content on 
  every `git clone`.
- Instead the file is hosted by GitHub's CDN
2020-10-06 16:35:58 -07:00
Ryan Richard
78cc49d658 Revert "supervisor-oidc: create dynamic config in YTT templates"
This reverts commit 006d96ab92.
2020-10-06 13:35:05 -07:00
Matt Moyer
8012d6a1c2 Merge pull request #147 from mattmoyer/oidc-cli
Implement initial steps of OIDC CLI client.
2020-10-06 15:20:30 -05:00
Matt Moyer
885005a3c1 Merge pull request #145 from mattmoyer/adjust-pr-template
Iterate on pull request template.
2020-10-06 15:20:01 -05:00
Matt Moyer
79c07f3e21 Merge pull request #146 from mattmoyer/tilt
Add Tilt-based local dev workflow.
2020-10-06 15:19:29 -05:00
Ryan Richard
14f1d86833 supervisor-oidc: add OIDCProviderConfig CRD
This will hopefully come in handy later if we ever decide to add
support for multiple OIDC providers as a part of one supervisor.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-06 15:20:29 -04:00
Ryan Richard
5b3dd5fc7d Rename pinniped-server -> pinniped-concierge
Do we like this? We don't know yet.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-06 14:59:03 -04:00
Matt Moyer
38501ff763 Add initial "pinniped alpha login oidc" partial implementation.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-06 12:42:29 -05:00
Andrew Keesler
006d96ab92 supervisor-oidc: create dynamic config in YTT templates
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-06 11:16:57 -04:00
Andrew Keesler
fd6a7f5892 supervisor-oidc: hoist OIDC discovery handler for testing
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-10-06 11:16:57 -04:00
Ryan Richard
76bd462cf8 Implement very rough skeleton of the start of a supervisor server
- This is just stab at a starting place because it felt easier to
  put something down on paper than to keep staring at a blank page
2020-10-05 17:28:19 -07:00
Matt Moyer
b0a4ae13c5 Add Tilt-based local dev workflow.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-05 16:34:33 -05:00
Matt Moyer
01153dcb9d Iterate on pull request template.
- Moves the "front matter" to a Markdown comment so you don't necessarily have to delete it.

- Reduces a little bit of boilerplate (this is a bit subjective).

- Tweaks some formatting (also subjective).

- Describe what happens when you use "Fixes [...]".

- Tweak the release note block so it should be easier to parse out automatically (using the same syntax as Kubernetes).

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-05 15:51:53 -05:00
Matt Moyer
c1c75a8f22 Add kube_cert_agent_image value to main ytt template.
This needs to be overridden for Tilt usage, since the main image referenced by Tilt isn't actually pullable.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-05 15:18:38 -05:00
Matt Moyer
7eed7ba19a Merge pull request #140 from mattmoyer/shuffle-contrib-docs
Rename the CoC and contributor guide to the names GitHub recognizes.
2020-10-05 11:24:59 -05:00
Ryan Richard
969c136921 Merge pull request #142 from vmware-tanzu/remove_curly_braces_from_doc_commands
Remove curly braces from doc commands
2020-10-02 17:45:50 -07:00
Ryan Richard
8a360fe08e Merge pull request #141 from danjahner/main
Minor docs updates
2020-10-02 17:20:44 -07:00
Ryan Richard
da695ef787 Remove curly braces from doc commands
Because when you copy/paste them to zsh they are automatically
escaped and then they do not work correctly
2020-10-02 16:44:27 -07:00
Dan Jahner
13e0b272c0 Docs only: Use consistent sample user name 2020-10-02 13:59:14 -07:00
Dan Jahner
e97bad2198 Docs only: Fix failure response, format for clarity 2020-10-02 13:58:31 -07:00
Matt Moyer
fe12f85c70 Rename the CoC and contributor guide to the names GitHub recognizes.
These special names are recognized by GitHub, for example on https://github.com/vmware-tanzu/pinniped/community.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
2020-10-02 15:53:48 -05:00
Dan Jahner
127914703e Docs only: Use prettier URL for latest config 2020-10-02 12:00:57 -07:00
Andrew Keesler
916db74d65 Merge pull request #138 from vmware-tanzu/no_dirty_in_version
Do not append "-dirty" to the version number when the git repo is dirty
2020-10-02 13:33:21 -04:00
Ryan Richard
0bfa351eb4 Do not append "-dirty" to the version number when the git repo is dirty
- Semver version numbers do not end with "-dirty"
- Continue to include the clean/dirty status of the repo in the
  `gitTreeState` field
2020-10-01 17:23:37 -07:00
Ryan Richard
b69eb5e850 Add link to GitHub Discussions on the main README 2020-09-29 16:46:18 -07:00
Ryan Richard
d43744f8e9 Allow CI to embed version info at build time for CLI
- Prevent the server binary from lying about its version number
  by having it report "?.?.?" as its version number for now.
- Later we can devise a way for CI to inject the version number
  for the server into the container image at release time,
  not at build time, since the version number is not known
  at build time.
- Pre-release builds of the binary from before the release stage or
  builds on developer workstation will also report "?.?.?" as its
  version number, which is fine since they are not official releases
  and shouldn't find their way to the public.
2020-09-28 09:58:02 -07:00
Andrew Keesler
d23ff1f5eb Merge pull request #136 from ankeesler/pinniped-version-command
cmd/pinniped: add version command
2020-09-28 12:21:25 -04:00
Andrew Keesler
d6571671f6 cmd/pinniped: add version command
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-09-28 10:44:33 -04:00
Andrew Keesler
38e26d7a49 test/library: use client-go anonymous rest config helper
I saw this helper function the other day and wondered if we could use it.
It does indeed look like it does what we want, because when I run this code,
I get `...User "system:anonymous" cannot get resource...`.

  c := library.NewAnonymousPinnipedClientset(t)
  _, err := c.
    ConfigV1alpha1().
    CredentialIssuerConfigs("integration").
    Get(context.Background(), "pinniped-config", metav1.GetOptions{})
  t.Log(err)

I also ran a similar test using this new helper in the context of
library.NewClientsetWithCertAndKey(). Seemed to get us what we want.

Signed-off-by: Andrew Keesler <akeesler@vmware.com>
2020-09-28 09:22:01 -04:00
1064 changed files with 69513 additions and 10766 deletions

View File

@@ -33,7 +33,7 @@ It is recommended that you include screenshots and logs to help everyone achieve
- Pinniped container image (if using a public container image):
- Pinniped configuration (what IDP(s) are you using? what downstream credential minting mechanisms are you using?):
- Kubernetes version (use `kubectl version`):
- Kubernetes installer & version (e.g., `kubeadm version`:
- Kubernetes installer & version (e.g., `kubeadm version`):
- Cloud provider or hardware configuration:
- OS (e.g: `cat /etc/os-release`):
- Kernel (e.g. `uname -a`):

View File

@@ -15,32 +15,21 @@ It is recommended that you include screenshots and logs to help everyone achieve
-->
**What is the problem that you wish to solve?**
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
> Please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**What is the best solution to the above problem?**
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
> Please provide a clear and concise description of what the solution is.
**Are you considering submitting a PR for this feature?**
**What are the alternative solutions that you have considered?**
- **How will this project improvement be tested?**
- **How does this change the current architecture?**
- **How will this change be backwards compatible?**
- **How will this feature be documented?**
> Please provide a clear and concise description of any alternative solutions or features you've considered.
**How will this project improvement be tested?**
> Please include your thoughts on how this improvement can be thoroughly tested.
**In what environment do you hope to see this improvement?**
- Pinniped server version:
- Pinniped client version:
- Pinniped container image (if using a public container image):
- Pinniped configuration (what IDP(s) are you using? what downstream credential minting mechanisms are you using?):
- Kubernetes version (use `kubectl version`):
- Kubernetes installer & version (e.g., `kubeadm version`:
- Cloud provider or hardware configuration:
- OS (e.g: `cat /etc/os-release`):
- Kernel (e.g. `uname -a`):
- Others:
**What else is there to know about this improvement?**
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,6 +1,7 @@
<!--
Thank you for submitting a pull request for Pinniped!
Before submitting, please see the guidelines in doc/contributing.md in this repo.
Before submitting, please see the guidelines in CONTRIBUTING.md in this repo.
Please note that a project maintainer will need to review and provide an
initial approval on the PR to cause CI tests to automatically start.
@@ -8,21 +9,33 @@ Also note that if you push additional commits to the PR, those commits
will need another initial approval before CI will pick them up.
Reminder: Did you remember to run all the linter, unit tests, and integration tests
described in doc/contributing.md on your branch before submitting this PR?
described in CONTRIBUTING.md on your branch before submitting this PR?
Below is a template to help you describe your PR.
-->
-- Please delete this line and all lines above this line before submitting the PR. Thanks! --
<!--
Provide a summary of your change. Feel free to use paragraphs or a bulleted list, for example:
**Summary of the changes included in this PR**
- Improves performance by 10,000%.
- Fixes all bugs.
- Boils the oceans.
**Issue(s) addressed by this PR**
- Fixes #
-->
**Things to consider while reviewing this PR**
<!--
Does this PR fix one or more reported issues?
If yes, use `Fixes #<issue number>` to automatically close the fixed issue(s) when the PR is merged.
-->
**Suggested release note for the first release which contains this PR**
**Release note**:
<!--
Does this PR introduce a user-facing change?
If no, just write "NONE" in the release-note block below.
If yes, a release note is required. Enter your extended release note in the block below.
-->
```release-note
```
release-note here
```

3
.gitignore vendored
View File

@@ -16,3 +16,6 @@
# goland
.idea
# Intermediate files used by Tilt
/hack/lib/tilt/build

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

@@ -1,4 +1,4 @@
exclude: '^generated/'
exclude: '^(generated|hack/lib/tilt/tilt_modules)/'
repos:
- repo: git://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
@@ -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

4
.pre-commit-hooks.yaml Normal file
View File

@@ -0,0 +1,4 @@
- id: validate-copyright-year
name: Validate copyright year
entry: hack/check-copyright-year.sh
language: script

View File

@@ -2,28 +2,40 @@
Contributions to Pinniped are welcome. Here are some things to help you get started.
1. Please see the [Code of Conduct](code_of_conduct.md).
1. Learn about the [scope](scope.md) of the project.
1. Coming soon: details about how to legally contribute to the project, including CLA/DCO details.
1. See below for how to [file a bug report](#bugs).
1. See below for how to [suggest a feature](#features).
1. See below for how to [build the code](#building).
1. See below for how to [run the tests](#testing).
## Code of Conduct
Please see the [Code of Conduct](./CODE_OF_CONDUCT.md).
## Project Scope
Learn about the [scope](https://pinniped.dev/docs/scope/) of the project.
## Meeting with the Maintainers
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)
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.
These meetings are currently scheduled for the first and third Thursday mornings of each month
at 8 AM Pacific Time, using this [Zoom meeting](https://VMware.zoom.us/j/94638309756?pwd=V3NvRXJIdDg5QVc0TUdFM2dYRzgrUT09).
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.
## Bugs
## 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.
## Issues
Need an idea for a project to get started contributing? Take a look at the open
[issues](https://github.com/vmware-tanzu/pinniped/issues).
Also check to see if any open issues are labeled with
["good first issue"](https://github.com/vmware-tanzu/pinniped/labels/good%20first%20issue)
or ["help wanted"](https://github.com/vmware-tanzu/pinniped/labels/help%20wanted).
### Bugs
To file a bug report, please first open an
[issue](https://github.com/vmware-tanzu/pinniped/issues/new?template=bug_report.md). The project team
@@ -35,11 +47,12 @@ can be opened to fix the bug.
For specifics on what to include in your bug report, please follow the
guidelines in the issue and pull request templates.
## Features
### Features
To suggest a feature, please first open an
[issue](https://github.com/vmware-tanzu/pinniped/issues/new?template=feature-proposal.md)
and tag it with `proposal`. The project team will work with you on your feature request.
and tag it with `proposal`, or create a new [Discussion](https://github.com/vmware-tanzu/pinniped/discussions).
The project team will work with you on your feature request.
Once the feature request has been validated, a [pull request](https://github.com/vmware-tanzu/pinniped/compare)
can be opened to implement the feature.
@@ -47,17 +60,6 @@ can be opened to implement the feature.
For specifics on what to include in your feature request, please follow the
guidelines in the issue and pull request templates.
## Issues
Github [issues](https://github.com/vmware-tanzu/pinniped/issues) can also be used for general
inquiries and discussion regarding the project.
Need an idea for a project to get started contributing? Take a look at the open
[issues](https://github.com/vmware-tanzu/pinniped/issues).
Also check to see if any open issues are labeled with
["good first issue"](https://github.com/vmware-tanzu/pinniped/labels/good%20first%20issue)
or ["help wanted"](https://github.com/vmware-tanzu/pinniped/labels/help%20wanted).
## CLA
We welcome contributions from everyone but we can only accept them if you sign
@@ -69,7 +71,7 @@ tracker.
## Building
The [Dockerfile](../Dockerfile) at the root of the repo can be used to build and
The [Dockerfile](Dockerfile) at the root of the repo can be used to build and
package the code. After making a change to the code, rebuild the docker image with the following command.
```bash
@@ -93,17 +95,51 @@ docker build .
### Running Integration Tests
```bash
./hack/prepare-for-integration-tests.sh && source /tmp/integration-test-env && go test -v -count 1 ./test/...
```
1. Install dependencies:
The `./hack/prepare-for-integration-tests.sh` script will create a local
[`kind`](https://kind.sigs.k8s.io/) cluster on which the integration tests will run.
- [`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)
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 k14s/tap/kapp kubectl chromedriver && brew cask install docker
```
1. Create a local Kubernetes cluster using `kind`:
```bash
./hack/kind-up.sh
```
1. Install Pinniped and supporting dependencies using `tilt`:
```bash
./hack/tilt-up.sh
```
Tilt will continue running and live-updating the Pinniped deployment whenever the code changes.
1. Run the Pinniped integration tests:
```bash
source /tmp/integration-test-env && go test -v -count 1 ./test/integration
```
To uninstall the test environment, run `./hack/tilt-down.sh`.
To destroy the local Kubernetes cluster, run `./hack/kind-down.sh`.
### Observing Tests on the Continuous Integration Environment
CI will not be triggered on a pull request until the pull request is reviewed and
approved for CI by a project [maintainer](../MAINTAINERS.md). Once CI is triggered,
[CI](https://hush-house.pivotal.io/teams/tanzu-user-auth/pipelines/pinniped-pull-requests)
will not be triggered on a pull request until the pull request is reviewed and
approved for CI by a project [maintainer](MAINTAINERS.md). Once CI is triggered,
the progress and results will appear on the Github page for that
[pull request](https://github.com/vmware-tanzu/pinniped/pulls) as checks. Links
will appear to view the details of each check.
@@ -129,4 +165,4 @@ pre-commit installed at .git/hooks/pre-commit
Regular contributors who are active in the Pinniped community and who have contributed at least several
significant pull requests may be considered for promotion to become a maintainer upon request. Please
contact an existing [maintainer](../MAINTAINERS.md) if you would like to be considered.
contact an existing [maintainer](MAINTAINERS.md) if you would like to be considered.

View File

@@ -1,37 +1,42 @@
# Copyright 2020 the Pinniped contributors. All Rights Reserved.
# Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
FROM golang:1.15.2 as build-env
FROM golang:1.15.6 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/
COPY generated/1.20/apis/go.* ./generated/1.20/apis/
COPY generated/1.20/client/go.* ./generated/1.20/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 pkg ./pkg
COPY tools ./tools
COPY internal ./internal
COPY hack ./hack
# 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-server/... \
&& 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/...
# Use a runtime image based on Debian slim
FROM debian:10.5-slim
FROM debian:10.7-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Copy the binaries from the build-env stage
COPY --from=build-env /work/out/pinniped-server /usr/local/bin/pinniped-server
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
# Document the port
EXPOSE 443
# Document the ports
EXPOSE 8080 8443
# Run as non-root for security posture
USER 1001:1001
# Set the entrypoint
ENTRYPOINT ["/usr/local/bin/pinniped-server"]
ENTRYPOINT ["/usr/local/bin/pinniped-concierge"]

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_with_text_on_right.svg" alt="Pinniped Logo" width="100%"/>
<img src="site/content/docs/img/pinniped_logo.svg" alt="Pinniped Logo" width="100%"/>
## Overview
@@ -23,22 +23,37 @@ 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/).
## 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.
## Contributions
Contributions are welcome. Before contributing, please see the [contributing guide](doc/contributing.md).
Contributions are welcome. Before contributing, please see the [contributing guide](CONTRIBUTING.md).
## Reporting Security Vulnerabilities

View File

@@ -0,0 +1,10 @@
// 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=authentication.concierge.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authentication API.
package v1alpha1

View File

@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "config.pinniped.dev"
const GroupName = "authentication.concierge.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
@@ -24,14 +24,16 @@ 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,
&CredentialIssuerConfig{},
&CredentialIssuerConfigList{},
&WebhookAuthenticator{},
&WebhookAuthenticatorList{},
&JWTAuthenticator{},
&JWTAuthenticatorList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -0,0 +1,81 @@
// 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
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
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

@@ -3,7 +3,7 @@
package v1alpha1
// Configuration for configuring TLS on various identity providers.
// Configuration for configuring TLS on various authenticators.
type TLSSpec struct {
// X.509 Certificate Authority (base64-encoded PEM bundle). If omitted, a default set of system roots will be trusted.
// +optional

View File

@@ -5,9 +5,9 @@ package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// Status of a webhook identity provider.
type WebhookIdentityProviderStatus struct {
// Represents the observations of an identity provider's current state.
// Status of a webhook authenticator.
type WebhookAuthenticatorStatus struct {
// Represents the observations of the authenticator's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
@@ -15,8 +15,8 @@ type WebhookIdentityProviderStatus struct {
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}
// Spec for configuring a webhook identity provider.
type WebhookIdentityProviderSpec struct {
// Spec for configuring a webhook authenticator.
type WebhookAuthenticatorSpec struct {
// Webhook server endpoint URL.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^https://`
@@ -27,27 +27,27 @@ type WebhookIdentityProviderSpec struct {
TLS *TLSSpec `json:"tls,omitempty"`
}
// WebhookIdentityProvider describes the configuration of a Pinniped webhook identity provider.
// WebhookAuthenticator describes the configuration of a webhook authenticator.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=all;idp;idps,shortName=webhookidp;webhookidps
// +kubebuilder:resource:categories=pinniped;pinniped-authenticator;pinniped-authenticators
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.spec.endpoint`
type WebhookIdentityProvider struct {
type WebhookAuthenticator struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec for configuring the identity provider.
Spec WebhookIdentityProviderSpec `json:"spec"`
// Spec for configuring the authenticator.
Spec WebhookAuthenticatorSpec `json:"spec"`
// Status of the identity provider.
Status WebhookIdentityProviderStatus `json:"status,omitempty"`
// Status of the authenticator.
Status WebhookAuthenticatorStatus `json:"status,omitempty"`
}
// List of WebhookIdentityProvider objects.
// List of WebhookAuthenticator objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type WebhookIdentityProviderList struct {
type WebhookAuthenticatorList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []WebhookIdentityProvider `json:"items"`
Items []WebhookAuthenticator `json:"items"`
}

View File

@@ -0,0 +1,10 @@
// 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=config.concierge.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration API.
package v1alpha1

View File

@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "idp.pinniped.dev"
const GroupName = "config.concierge.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
@@ -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,
&WebhookIdentityProvider{},
&WebhookIdentityProviderList{},
&CredentialIssuer{},
&CredentialIssuerList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

View File

@@ -25,17 +25,17 @@ const (
)
// Status of a credential issuer.
type CredentialIssuerConfigStatus struct {
type CredentialIssuerStatus struct {
// List of integration strategies that were attempted by Pinniped.
Strategies []CredentialIssuerConfigStrategy `json:"strategies"`
Strategies []CredentialIssuerStrategy `json:"strategies"`
// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
// +optional
KubeConfigInfo *CredentialIssuerConfigKubeConfigInfo `json:"kubeConfigInfo,omitempty"`
KubeConfigInfo *CredentialIssuerKubeConfigInfo `json:"kubeConfigInfo,omitempty"`
}
// Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
type CredentialIssuerConfigKubeConfigInfo struct {
type CredentialIssuerKubeConfigInfo struct {
// The K8s API server URL.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^https://|^http://`
@@ -47,7 +47,7 @@ type CredentialIssuerConfigKubeConfigInfo struct {
}
// Status of an integration strategy that was attempted by Pinniped.
type CredentialIssuerConfigStrategy struct {
type CredentialIssuerStrategy struct {
// Type of integration attempted.
Type StrategyType `json:"type"`
@@ -65,27 +65,23 @@ type CredentialIssuerConfigStrategy struct {
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
}
// Describes the configuration status of a Pinniped credential issuer.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:shortName=cic
type CredentialIssuerConfig struct {
// +kubebuilder:resource:categories=pinniped
type CredentialIssuer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Status of the credential issuer.
Status CredentialIssuerConfigStatus `json:"status"`
Status CredentialIssuerStatus `json:"status"`
}
// List of CredentialIssuerConfig objects.
// List of CredentialIssuer objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CredentialIssuerConfigList struct {
type CredentialIssuerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CredentialIssuerConfig `json:"items"`
Items []CredentialIssuer `json:"items"`
}

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// +k8s:deepcopy-gen=package
// +groupName=login.pinniped.dev
// +groupName=login.concierge.pinniped.dev
// Package login is the internal version of the Pinniped login API.
package login

View File

@@ -8,7 +8,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "login.pinniped.dev"
const GroupName = "login.concierge.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}

View File

@@ -12,8 +12,8 @@ type TokenCredentialRequestSpec struct {
// Bearer token supplied with the credential request.
Token string
// Reference to an identity provider which can fulfill this credential request.
IdentityProvider corev1.TypedLocalObjectReference
// Reference to an authenticator which can validate this credential request.
Authenticator corev1.TypedLocalObjectReference
}
type TokenCredentialRequestStatus struct {

View File

@@ -3,9 +3,9 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/login
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/concierge/login
// +k8s:defaulter-gen=TypeMeta
// +groupName=login.pinniped.dev
// +groupName=login.concierge.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped login API.
package v1alpha1

View File

@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "login.pinniped.dev"
const GroupName = "login.concierge.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}

View File

@@ -13,8 +13,8 @@ type TokenCredentialRequestSpec struct {
// Bearer token supplied with the credential request.
Token string `json:"token,omitempty"`
// Reference to an identity provider which can fulfill this credential request.
IdentityProvider corev1.TypedLocalObjectReference `json:"identityProvider"`
// Reference to an authenticator which can validate this credential request.
Authenticator corev1.TypedLocalObjectReference `json:"authenticator"`
}
// TokenCredentialRequestStatus is the status of a TokenCredentialRequest, returned on responses to the Pinniped API.

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.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,11 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/config
// +k8s:defaulter-gen=TypeMeta
// +groupName=config.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped configuration API.
package 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=idp.pinniped.dev
// Package idp is the internal version of the Pinniped identity provider API.
package idp

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

@@ -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

@@ -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:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/supervisor/config
// +k8s:defaulter-gen=TypeMeta
// +groupName=config.supervisor.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor configuration 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 = "config.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,
&FederationDomain{},
&FederationDomainList{},
)
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,130 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
)
// 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 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.
//
// When your Issuer URL's host is an IP address, then this field is ignored. SNI does not work for IP addresses.
//
// +optional
SecretName string `json:"secretName,omitempty"`
}
// 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
// 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.
// +kubebuilder:validation:MinLength=1
Issuer string `json:"issuer"`
// TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
// +optional
TLS *FederationDomainTLSSpec `json:"tls,omitempty"`
}
// 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 FederationDomainStatusCondition `json:"status,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// 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).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
Secrets FederationDomainSecrets `json:"secrets,omitempty"`
}
// FederationDomain describes the configuration of an OIDC provider.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
type FederationDomain struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec of the OIDC provider.
Spec FederationDomainSpec `json:"spec"`
// Status of the OIDC provider.
Status FederationDomainStatus `json:"status,omitempty"`
}
// List of FederationDomain objects.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type FederationDomainList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FederationDomain `json:"items"`
}

View File

@@ -3,10 +3,9 @@
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=go.pinniped.dev/GENERATED_PKG/apis/idp
// +k8s:defaulter-gen=TypeMeta
// +groupName=idp.pinniped.dev
// +groupName=idp.supervisor.pinniped.dev
// +groupGoName=IDP
// Package v1alpha1 is the v1alpha1 version of the Pinniped identity provider API.
// 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,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)
}
}
}()
@@ -109,18 +110,18 @@ func (w *webhook) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
if err != nil {
return
}
defer req.Body.Close()
defer func() { _ = req.Body.Close() }()
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,
@@ -310,6 +296,9 @@ func startControllers(
apicerts.NewCertsManagerController(
namespace,
certsSecretResourceName,
map[string]string{
"app": "local-user-authenticator",
},
kubeClient,
kubeInformers.Core().V1().Secrets(),
controllerlib.WithInformer,
@@ -355,42 +344,47 @@ 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", ":443")
l, err := net.Listen("tcp", ":8443")
if err != nil {
return fmt.Errorf("cannot create listener: %w", err)
}
defer l.Close()
defer func() { _ = l.Close() }()
err = startWebhook(ctx, l, dynamicCertProvider, kubeInformers.Core().V1().Secrets())
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

@@ -106,7 +106,7 @@ func TestWebhook(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer l.Close()
defer func() { _ = l.Close() }()
require.NoError(t, w.start(ctx, l))
client := newClient(caBundle, serverName)
@@ -412,7 +412,7 @@ func TestWebhook(t *testing.T) {
Body: body,
})
require.NoError(t, err)
defer rsp.Body.Close()
defer func() { _ = rsp.Body.Close() }()
require.Equal(t, test.wantStatus, rsp.StatusCode)
@@ -470,7 +470,7 @@ func newCertProvider(t *testing.T) (dynamiccert.Provider, []byte, string) {
ca, err := certauthority.New(pkix.Name{CommonName: serverName + " CA"}, time.Hour*24)
require.NoError(t, err)
cert, err := ca.Issue(pkix.Name{CommonName: serverName}, []string{serverName}, time.Hour*24)
cert, err := ca.Issue(pkix.Name{CommonName: serverName}, []string{serverName}, nil, time.Hour*24)
require.NoError(t, err)
certPEM, keyPEM, err := certauthority.ToPEM(cert)

View File

@@ -5,6 +5,7 @@ package main
import (
"os"
"time"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/client-go/pkg/version"
@@ -12,14 +13,19 @@ import (
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"go.pinniped.dev/internal/server"
"go.pinniped.dev/internal/concierge/server"
)
func main() {
logs.InitLogs()
defer logs.FlushLogs()
klog.Infof("Running %s at %#v", rest.DefaultKubernetesUserAgent(), version.Get())
// Dump out the time since compile (mostly useful for benchmarking our local development cycle latency).
var timeSinceCompile time.Duration
if buildDate, err := time.Parse(time.RFC3339, version.Get().BuildDate); err == nil {
timeSinceCompile = time.Since(buildDate).Round(time.Second)
}
klog.Infof("Running %s at %#v (%s since build)", rest.DefaultKubernetesUserAgent(), version.Get(), timeSinceCompile)
ctx := genericapiserver.SetupSignalContext()

View File

@@ -0,0 +1,384 @@
// 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"
"net/http"
"os"
"os/signal"
"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"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"k8s.io/klog/v2/klogr"
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/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 (
singletonWorker = 1
defaultResyncInterval = 3 * time.Minute
)
func start(ctx context.Context, l net.Listener, handler http.Handler) {
server := http.Server{Handler: handler}
errCh := make(chan error)
go func() {
errCh <- server.Serve(l)
}()
go func() {
select {
case err := <-errCh:
plog.Debug("server exited", "err", err)
case <-ctx.Done():
plog.Debug("server context cancelled", "err", ctx.Err())
if err := server.Shutdown(context.Background()); err != nil {
plog.Debug("server shutdown failed", "err", err)
}
}
}()
}
func waitForSignal() os.Signal {
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt)
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(
supervisorstorage.GarbageCollectorController(
clock.RealClock{},
kubeClient,
secretInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
supervisorconfig.NewFederationDomainWatcherController(
issuerManager,
clock.RealClock{},
pinnipedClient,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
supervisorconfig.NewJWKSWriterController(
cfg.Labels,
kubeClient,
pinnipedClient,
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
supervisorconfig.NewJWKSObserverController(
dynamicJWKSProvider,
secretInformer,
federationDomainInformer,
controllerlib.WithInformer,
),
singletonWorker,
).
WithController(
supervisorconfig.NewTLSCertObserverController(
dynamicTLSCertProvider,
cfg.NamesConfig.DefaultTLSCertificateSecret,
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.FederationDomain) *corev1.LocalObjectReference {
return &fd.Status.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.FederationDomain) *corev1.LocalObjectReference {
return &fd.Status.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.FederationDomain) *corev1.LocalObjectReference {
return &fd.Status.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())
// Wait until the caches are synced before returning.
kubeInformers.WaitForCacheSync(ctx.Done())
pinnipedInformers.WaitForCacheSync(ctx.Done())
go controllerManager.Start(ctx)
}
func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
serverInstallationNamespace := podInfo.Namespace
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// TODO remove code that relies on supervisorDeployment directly
dref, supervisorDeployment, err := deploymentref.New(podInfo)
if err != nil {
return fmt.Errorf("cannot create deployment ref: %w", err)
}
client, err := kubeclient.New(dref)
if err != nil {
return fmt.Errorf("cannot create k8s client: %w", err)
}
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
client.Kubernetes,
defaultResyncInterval,
kubeinformers.WithNamespace(serverInstallationNamespace),
)
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
client.PinnipedSupervisor,
defaultResyncInterval,
pinnipedinformers.WithNamespace(serverInstallationNamespace),
)
// Serve the /healthz endpoint and make all other paths result in 404.
healthMux := http.NewServeMux()
healthMux.Handle("/healthz", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
_, _ = writer.Write([]byte("ok"))
}))
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,
dynamicUpstreamIDPProvider,
&secretCache,
client.Kubernetes.CoreV1().Secrets(serverInstallationNamespace),
)
startControllers(
ctx,
cfg,
oidProvidersManager,
dynamicJWKSProvider,
dynamicTLSCertProvider,
dynamicUpstreamIDPProvider,
&secretCache,
supervisorDeployment,
client.Kubernetes,
client.PinnipedSupervisor,
kubeInformers,
pinnipedInformers,
)
//nolint: gosec // Intentionally binding to all network interfaces.
httpListener, err := net.Listen("tcp", ":8080")
if err != nil {
return fmt.Errorf("cannot create listener: %w", err)
}
defer func() { _ = httpListener.Close() }()
start(ctx, httpListener, oidProvidersManager)
//nolint: gosec // Intentionally binding to all network interfaces.
httpsListener, err := tls.Listen("tcp", ":8443", &tls.Config{
MinVersion: tls.VersionTLS12, // Allow v1.2 because clients like the default `curl` on MacOS don't support 1.3 yet.
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
cert := dynamicTLSCertProvider.GetTLSCert(strings.ToLower(info.ServerName))
defaultCert := dynamicTLSCertProvider.GetDefaultTLSCert()
plog.Debug("GetCertificate called for port 8443",
"info.ServerName", info.ServerName,
"foundSNICert", cert != nil,
"foundDefaultCert", defaultCert != nil,
)
if cert == nil {
cert = defaultCert
}
return cert, nil
},
})
if err != nil {
return fmt.Errorf("cannot create listener: %w", err)
}
defer func() { _ = httpsListener.Close() }()
start(ctx, httpsListener, oidProvidersManager)
plog.Debug("supervisor is ready",
"httpAddress", httpListener.Addr().String(),
"httpsAddress", httpsListener.Addr().String(),
)
gotSignal := waitForSignal()
plog.Debug("supervisor exiting", "signal", gotSignal)
return nil
}
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])
// Discover in which namespace we are installed.
podInfo, err := downward.Load(os.Args[1])
if err != nil {
klog.Fatal(fmt.Errorf("could not read pod metadata: %w", err))
}
// Read the server config file.
cfg, err := supervisor.FromPath(os.Args[2])
if err != nil {
klog.Fatal(fmt.Errorf("could not load config: %w", err))
}
if err := run(podInfo, cfg); err != nil {
klog.Fatal(err)
}
}

22
cmd/pinniped/cmd/alpha.go Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"github.com/spf13/cobra"
)
//nolint: gochecknoglobals
var alphaCmd = &cobra.Command{
Use: "alpha",
Short: "alpha",
Long: "alpha subcommands (syntax or flags are still subject to change)",
SilenceUsage: true, // do not print usage message when commands fail
Hidden: true,
}
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(alphaCmd)
}

View File

@@ -0,0 +1,24 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import "github.com/spf13/cobra"
// mustMarkRequired marks the given flags as required on the provided cobra.Command. If any of the names are wrong, it panics.
func mustMarkRequired(cmd *cobra.Command, flags ...string) {
for _, flag := range flags {
if err := cmd.MarkFlagRequired(flag); err != nil {
panic(err)
}
}
}
// mustMarkHidden marks the given flags as hidden on the provided cobra.Command. If any of the names are wrong, it panics.
func mustMarkHidden(cmd *cobra.Command, flags ...string) {
for _, flag := range flags {
if err := cmd.Flags().MarkHidden(flag); err != nil {
panic(err)
}
}
}

View File

@@ -0,0 +1,21 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
)
func TestMustMarkRequired(t *testing.T) {
require.NotPanics(t, func() { mustMarkRequired(&cobra.Command{}) })
require.NotPanics(t, func() {
cmd := &cobra.Command{}
cmd.Flags().String("known-flag", "", "")
mustMarkRequired(cmd, "known-flag")
})
require.Panics(t, func() { mustMarkRequired(&cobra.Command{}, "unknown-flag") })
}

View File

@@ -0,0 +1,136 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"encoding/base64"
"fmt"
"os"
"github.com/spf13/cobra"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog"
)
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(legacyGetKubeconfigCommand(kubeconfigRealDeps()))
rootCmd.AddCommand(legacyExchangeTokenCommand(staticLoginRealDeps()))
}
func legacyGetKubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
var (
cmd = &cobra.Command{
Hidden: true,
Deprecated: "Please use `pinniped get kubeconfig` instead.",
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
`),
}
token string
kubeconfig string
contextOverride string
namespace string
authenticatorType string
authenticatorName string
)
cmd.Flags().StringVar(&token, "token", "", "Credential to include in the resulting kubeconfig output (Required)")
cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to the kubeconfig file")
cmd.Flags().StringVar(&contextOverride, "kubeconfig-context", "", "Kubeconfig context override")
cmd.Flags().StringVar(&namespace, "pinniped-namespace", "pinniped-concierge", "Namespace in which Pinniped was installed")
cmd.Flags().StringVar(&authenticatorType, "authenticator-type", "", "Authenticator type (e.g., 'webhook', 'jwt')")
cmd.Flags().StringVar(&authenticatorName, "authenticator-name", "", "Authenticator name")
mustMarkRequired(cmd, "token")
plog.RemoveKlogGlobalFlags()
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return runGetKubeconfig(cmd.OutOrStdout(), deps, getKubeconfigParams{
kubeconfigPath: kubeconfig,
kubeconfigContextOverride: contextOverride,
staticToken: token,
concierge: getKubeconfigConciergeParams{
namespace: namespace,
authenticatorName: authenticatorName,
authenticatorType: authenticatorType,
},
})
}
return cmd
}
func legacyExchangeTokenCommand(deps staticLoginDeps) *cobra.Command {
cmd := &cobra.Command{
Hidden: true,
Deprecated: "Please use `pinniped login static` instead.",
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", "jwt")
- 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
`),
}
plog.RemoveKlogGlobalFlags()
cmd.RunE = func(cmd *cobra.Command, args []string) error {
// Make a little helper to grab OS environment variables and keep a list that were missing.
var missing []string
getEnv := func(name string) string {
value, ok := os.LookupEnv(name)
if !ok {
missing = append(missing, name)
}
return value
}
flags := staticLoginParams{
staticToken: getEnv("PINNIPED_TOKEN"),
conciergeEnabled: true,
conciergeNamespace: getEnv("PINNIPED_NAMESPACE"),
conciergeAuthenticatorType: getEnv("PINNIPED_AUTHENTICATOR_TYPE"),
conciergeAuthenticatorName: getEnv("PINNIPED_AUTHENTICATOR_NAME"),
conciergeEndpoint: getEnv("PINNIPED_K8S_API_ENDPOINT"),
conciergeCABundle: base64.StdEncoding.EncodeToString([]byte(getEnv("PINNIPED_CA_BUNDLE"))),
}
if len(missing) > 0 {
return fmt.Errorf("failed to get credential: required environment variable(s) not set: %v", missing)
}
return runStaticLogin(cmd.OutOrStdout(), deps, flags)
}
return cmd
}

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"
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/idp/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 identity provider to authenticate
against
- PINNIPED_IDP_TYPE: the type of identity provider to authenticate
against (e.g., "webhook")
- PINNIPED_IDP_NAME: the name of the identity provider 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,
idp corev1.TypedLocalObjectReference,
token string,
caBundle string,
apiEndpoint string,
) (*clientauthenticationv1beta1.ExecCredential, error)
const (
ErrMissingEnvVar = constable.Error("failed to get credential: environment variable not set")
ErrInvalidIDPType = constable.Error("invalid IDP 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")
}
idpType, varExists := envGetter("PINNIPED_IDP_TYPE")
if !varExists {
return envVarNotSetError("PINNIPED_IDP_TYPE")
}
idpName, varExists := envGetter("PINNIPED_IDP_NAME")
if !varExists {
return envVarNotSetError("PINNIPED_IDP_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")
}
idp := corev1.TypedLocalObjectReference{Name: idpName}
switch strings.ToLower(idpType) {
case "webhook":
idp.APIGroup = &idpv1alpha1.SchemeGroupVersion.Group
idp.Kind = "WebhookIdentityProvider"
default:
return fmt.Errorf(`%w: %q, supported values are "webhook"`, ErrInvalidIDPType, idpType)
}
cred, err := tokenExchanger(ctx, namespace, idp, 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 identity provider to authenticate
against
- PINNIPED_IDP_TYPE: the type of identity provider to authenticate
against (e.g., "webhook")
- PINNIPED_IDP_NAME: the name of the identity provider 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_IDP_TYPE": "Webhook",
"PINNIPED_IDP_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_IDP_TYPE is missing", func() {
delete(fakeEnv, "PINNIPED_IDP_TYPE")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_IDP_TYPE")
})
it("returns an error when PINNIPED_IDP_NAME is missing", func() {
delete(fakeEnv, "PINNIPED_IDP_NAME")
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_IDP_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_IDP_TYPE is missing", func() {
fakeEnv["PINNIPED_IDP_TYPE"] = "invalid"
err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second)
r.EqualError(err, `invalid IDP type: "invalid", supported values are "webhook"`)
})
})
when("the token exchange fails", func() {
it.Before(func() {
tokenExchanger = func(ctx context.Context, namespace string, idp 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, idp 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, idp 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, idp 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,347 +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/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/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
idpName string
idpType 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)")
err := cmd.MarkFlagRequired("token")
if err != nil {
panic(err)
}
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.idpType, "idp-type", c.flags.idpType, "Identity provider type (e.g., 'webhook')")
cmd.Flags().StringVar(&c.flags.idpName, "idp-name", c.flags.idpType, "Identity provider name")
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
}
idpType, idpName := c.flags.idpType, c.flags.idpName
if idpType == "" || idpName == "" {
idpType, idpName, err = getDefaultIDP(clientset, c.flags.namespace)
if err != nil {
return err
}
}
credentialIssuerConfig, err := fetchPinnipedCredentialIssuerConfig(clientset, c.flags.namespace)
if err != nil {
return err
}
if credentialIssuerConfig.Status.KubeConfigInfo == nil {
return constable.Error(`CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`)
}
v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, c.flags.contextOverride)
if err != nil {
return err
}
err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuerConfig, cmd.ErrOrStderr())
if err != nil {
return err
}
config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, c.flags.token, c.flags.namespace, idpType, idpName)
err = writeConfigAsYAML(cmd.OutOrStdout(), config)
if err != nil {
return err
}
return nil
}
func issueWarningForNonMatchingServerOrCA(v1Cluster v1.Cluster, credentialIssuerConfig *configv1alpha1.CredentialIssuerConfig, warningsWriter io.Writer) error {
credentialIssuerConfigCA, err := base64.StdEncoding.DecodeString(credentialIssuerConfig.Status.KubeConfigInfo.CertificateAuthorityData)
if err != nil {
return err
}
if v1Cluster.Server != credentialIssuerConfig.Status.KubeConfigInfo.Server ||
!bytes.Equal(v1Cluster.CertificateAuthorityData, credentialIssuerConfigCA) {
_, err := warningsWriter.Write([]byte("WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuerConfig on the cluster. Using local kubeconfig values.\n"))
if err != nil {
return fmt.Errorf("output write error: %w", err)
}
}
return nil
}
type noIDPError struct{ Namespace string }
func (e noIDPError) Error() string {
return fmt.Sprintf(`no identity providers were found in namespace %q`, e.Namespace)
}
type indeterminateIDPError struct{ Namespace string }
func (e indeterminateIDPError) Error() string {
return fmt.Sprintf(
`multiple identity providers were found in namespace %q, so --pinniped-idp-name/--pinniped-idp-type must be specified`,
e.Namespace,
)
}
func getDefaultIDP(clientset pinnipedclientset.Interface, namespace string) (string, string, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
webhooks, err := clientset.IDPV1alpha1().WebhookIdentityProviders(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return "", "", err
}
type ref struct{ idpType, idpName string }
idps := make([]ref, 0, len(webhooks.Items))
for _, webhook := range webhooks.Items {
idps = append(idps, ref{idpType: "webhook", idpName: webhook.Name})
}
if len(idps) == 0 {
return "", "", noIDPError{namespace}
}
if len(idps) > 1 {
return "", "", indeterminateIDPError{namespace}
}
return idps[0].idpType, idps[0].idpName, nil
}
func fetchPinnipedCredentialIssuerConfig(clientset pinnipedclientset.Interface, pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuerConfig, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc()
credentialIssuerConfigs, err := clientset.ConfigV1alpha1().CredentialIssuerConfigs(pinnipedInstallationNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
if len(credentialIssuerConfigs.Items) == 0 {
return nil, constable.Error(fmt.Sprintf(
`No CredentialIssuerConfig was found in namespace "%s". Is Pinniped installed on this cluster in namespace "%s"?`,
pinnipedInstallationNamespace,
pinnipedInstallationNamespace,
))
}
if len(credentialIssuerConfigs.Items) > 1 {
return nil, constable.Error(fmt.Sprintf(
`More than one CredentialIssuerConfig was found in namespace "%s"`,
pinnipedInstallationNamespace,
))
}
return &credentialIssuerConfigs.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, idpType string, idpName 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_IDP_TYPE",
Value: idpType,
},
{
Name: "PINNIPED_IDP_NAME",
Value: idpName,
},
},
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"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
idpv1alpha "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
"go.pinniped.dev/internal/here"
)
var (
knownGoodUsageForGetKubeConfig = here.Doc(`
Usage:
get-kubeconfig [flags]
Flags:
-h, --help help for get-kubeconfig
--idp-name string Identity provider name
--idp-type string Identity provider type (e.g., 'webhook')
--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:
-h, --help help for get-kubeconfig
--idp-name string Identity provider name
--idp-type string Identity provider type (e.g., 'webhook')
--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
idpType string
idpName 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_IDP_TYPE
value: %s
- name: PINNIPED_IDP_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.idpType, e.idpName)
}
func newCredentialIssuerConfig(name, namespace, server, certificateAuthorityData string) *configv1alpha1.CredentialIssuerConfig {
return &configv1alpha1.CredentialIssuerConfig{
TypeMeta: metav1.TypeMeta{
Kind: "CredentialIssuerConfig",
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Status: configv1alpha1.CredentialIssuerConfigStatus{
KubeConfigInfo: &configv1alpha1.CredentialIssuerConfigKubeConfigInfo{
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 IDPs",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.idpName = ""
cmd.flags.idpType = ""
clientset := pinnipedfake.NewSimpleClientset()
clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some error getting IDPs")
})
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return clientset, nil
}
},
wantError: "some error getting IDPs",
},
{
name: "zero IDPs",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.idpName = ""
cmd.flags.idpType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(), nil
}
},
wantError: `no identity providers were found in namespace "test-namespace"`,
},
{
name: "multiple IDPs",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.idpName = ""
cmd.flags.idpType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-one"}},
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "webhook-two"}},
), nil
}
},
wantError: `multiple identity providers were found in namespace "test-namespace", so --pinniped-idp-name/--pinniped-idp-type must be specified`,
},
{
name: "fail to get CredentialIssuerConfigs",
mocks: func(cmd *getKubeConfigCommand) {
clientset := pinnipedfake.NewSimpleClientset()
clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, fmt.Errorf("some error getting CredentialIssuerConfigs")
})
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return clientset, nil
}
},
wantError: "some error getting CredentialIssuerConfigs",
},
{
name: "zero CredentialIssuerConfigs found",
mocks: func(cmd *getKubeConfigCommand) {
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
newCredentialIssuerConfig("pinniped-config-1", "not-the-test-namespace", "", ""),
), nil
}
},
wantError: `No CredentialIssuerConfig was found in namespace "test-namespace". Is Pinniped installed on this cluster in namespace "test-namespace"?`,
},
{
name: "multiple CredentialIssuerConfigs found",
mocks: func(cmd *getKubeConfigCommand) {
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
newCredentialIssuerConfig("pinniped-config-1", "test-namespace", "", ""),
newCredentialIssuerConfig("pinniped-config-2", "test-namespace", "", ""),
), nil
}
},
wantError: `More than one CredentialIssuerConfig was found in namespace "test-namespace"`,
},
{
name: "CredentialIssuerConfig missing KubeConfigInfo",
mocks: func(cmd *getKubeConfigCommand) {
cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "", "")
cic.Status.KubeConfigInfo = nil
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(cic), nil
}
},
wantError: `CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`,
},
{
name: "KubeConfigInfo has invalid base64",
mocks: func(cmd *getKubeConfigCommand) {
cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://example.com", "")
cic.Status.KubeConfigInfo.CertificateAuthorityData = "invalid-base64-test-ca"
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(cic), nil
}
},
wantError: `illegal base64 data at input byte 7`,
},
{
name: "success using remote CA data",
mocks: func(cmd *getKubeConfigCommand) {
cic := newCredentialIssuerConfig("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(cic), 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",
idpType: "test-idp-type",
idpName: "test-idp-name",
}.String(),
},
{
name: "success using local CA data and discovered IDP",
mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.idpName = ""
cmd.flags.idpType = ""
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
&idpv1alpha.WebhookIdentityProvider{ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "discovered-idp"}},
newCredentialIssuerConfig("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 CredentialIssuerConfig 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",
idpType: "webhook",
idpName: "discovered-idp",
}.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.idpName = "test-idp-name"
c.flags.idpType = "test-idp-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,371 @@
// 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"
"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/kubeclient"
)
type kubeconfigDeps struct {
getPathToSelf func() (string, error)
getClientset func(clientcmd.ClientConfig) (conciergeclientset.Interface, error)
}
func kubeconfigRealDeps() kubeconfigDeps {
return kubeconfigDeps{
getPathToSelf: os.Executable,
getClientset: func(clientConfig clientcmd.ClientConfig) (conciergeclientset.Interface, error) {
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
client, err := kubeclient.New(kubeclient.WithConfig(restConfig))
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
namespace string
authenticatorName string
authenticatorType 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
)
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(&flags.concierge.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.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")
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 {
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)
if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err)
}
if !flags.concierge.disabled {
authenticator, err := lookupAuthenticator(
clientset,
flags.concierge.namespace,
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/%s has invalid spec.tls.certificateAuthorityData: %w", auth.Namespace, 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-namespace="+flags.concierge.namespace,
"--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, namespace, 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(namespace).Get(ctx, authName, metav1.GetOptions{})
case "jwt":
return clientset.AuthenticationV1alpha1().JWTAuthenticators(namespace).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(namespace).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(namespace).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 in namespace %q (try setting --concierge-namespace)", namespace)
}
if len(results) > 1 {
return nil, fmt.Errorf("multiple authenticators were found in namespace %q, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified", namespace)
}
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,528 @@
// 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
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: here.Doc(`
Generate a Pinniped-based kubeconfig for a cluster
Usage:
kubeconfig [flags]
Flags:
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
-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 in namespace "pinniped-concierge" (try setting --concierge-namespace)
`),
},
{
name: "fail to autodetect authenticator, multiple found",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-namespace", "test-namespace",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-1", Namespace: "test-namespace"}},
&conciergev1alpha1.JWTAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-2", Namespace: "test-namespace"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-3", Namespace: "test-namespace"}},
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4", Namespace: "test-namespace"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: multiple authenticators were found in namespace "test-namespace", 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",
"--concierge-namespace", "test-namespace",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
},
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",
"--concierge-namespace", "test-namespace",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.JWTAuthenticator{
ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"},
Spec: conciergev1alpha1.JWTAuthenticatorSpec{
TLS: &conciergev1alpha1.TLSSpec{
CertificateAuthorityData: "invalid-base64",
},
},
},
},
wantError: true,
wantStderr: here.Doc(`
Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-namespace/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",
"--concierge-namespace", "test-namespace",
"--static-token", "test-token",
"--static-token-env", "TEST_TOKEN",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
},
wantError: true,
wantStderr: here.Doc(`
Error: only one of --static-token and --static-token-env can be specified
`),
},
{
name: "valid static token",
args: []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--concierge-namespace", "test-namespace",
"--static-token", "test-token",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
},
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-namespace=test-namespace
- --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",
"--concierge-namespace", "test-namespace",
"--static-token-env", "TEST_TOKEN",
},
conciergeObjects: []runtime.Object{
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
},
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-namespace=test-namespace
- --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", Namespace: "pinniped-concierge"},
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-namespace=pinniped-concierge
- --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-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", Namespace: "pinniped-concierge"},
},
},
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-namespace=pinniped-concierge
- --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())),
},
}
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) (conciergeclientset.Interface, error) {
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")
})
}
}

22
cmd/pinniped/cmd/login.go Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"github.com/spf13/cobra"
)
//nolint: gochecknoglobals
var loginCmd = &cobra.Command{
Use: "login",
Short: "login",
Long: "Login to a Pinniped server",
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
func init() {
rootCmd.AddCommand(loginCmd)
}

View File

@@ -0,0 +1,237 @@
// Copyright 2020 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"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/klog/v2/klogr"
"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(oidcLoginCommandRealDeps()))
}
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
conciergeNamespace string
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle string
}
func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
var (
cmd = cobra.Command{
Args: cobra.NoArgs,
Use: "oidc --issuer ISSUER",
Short: "Login using an OpenID Connect provider",
SilenceUsage: true,
}
flags oidcLoginFlags
)
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(&flags.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")
mustMarkHidden(&cmd, "debug-session-cache")
mustMarkRequired(&cmd, "issuer")
cmd.RunE = func(cmd *cobra.Command, args []string) error { return runOIDCLogin(cmd, deps, flags) }
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.WithNamespace(flags.conciergeNamespace),
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
)
if err != nil {
return fmt.Errorf("invalid concierge parameters: %w", err)
}
}
// --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 len(flags.caBundlePaths) > 0 || len(flags.caBundleData) > 0 {
client, err := makeClient(flags.caBundlePaths, flags.caBundleData)
if err != nil {
return err
}
opts = append(opts, oidcclient.WithClient(client))
}
// 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:
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should
// be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
// [1] https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
func mustGetConfigDir() string {
const xdgAppName = "pinniped"
if path := os.Getenv("XDG_CONFIG_HOME"); path != "" {
return filepath.Join(path, xdgAppName)
}
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
return filepath.Join(home, ".config", xdgAppName)
}

View File

@@ -0,0 +1,237 @@
// Copyright 2020 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/testutil"
"go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
func TestLoginOIDCCommand(t *testing.T) {
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
wantOptionsCount int
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: here.Doc(`
Login using an OpenID Connect provider
Usage:
oidc --issuer ISSUER [flags]
Flags:
--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-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
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
--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,
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: "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
`),
},
{
name: "success with minimal options",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
},
wantOptionsCount: 3,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
},
{
name: "success with all options",
args: []string{
"--client-id", "test-client-id",
"--issuer", "test-issuer",
"--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-namespace", "test-namespace",
"--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()),
},
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) {
var (
gotOptions []oidcclient.Option
)
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)
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")
require.Len(t, gotOptions, tt.wantOptionsCount)
})
}
}

View File

@@ -0,0 +1,120 @@
// Copyright 2020 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
conciergeNamespace string
conciergeAuthenticatorType string
conciergeAuthenticatorName string
conciergeEndpoint string
conciergeCABundle 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
)
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(&flags.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.RunE = func(cmd *cobra.Command, args []string) error { return runStaticLogin(cmd.OutOrStdout(), deps, flags) }
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.WithNamespace(flags.conciergeNamespace),
conciergeclient.WithEndpoint(flags.conciergeEndpoint),
conciergeclient.WithBase64CABundle(flags.conciergeCABundle),
conciergeclient.WithAuthenticator(flags.conciergeAuthenticatorType, flags.conciergeAuthenticatorName),
)
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,180 @@
// Copyright 2020 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-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
--concierge-namespace string Namespace in which the concierge was installed (default "pinniped-concierge")
--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: "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

@@ -4,7 +4,6 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
@@ -22,7 +21,6 @@ var rootCmd = &cobra.Command{
// 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

@@ -0,0 +1,28 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"k8s.io/component-base/version"
)
//nolint: gochecknoinits
func init() {
rootCmd.AddCommand(newVersionCommand())
}
func newVersionCommand() *cobra.Command {
return &cobra.Command{
RunE: func(cmd *cobra.Command, _ []string) error {
fmt.Fprintf(cmd.OutOrStdout(), "%#v\n", version.Get())
return nil
},
Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "version",
Short: "Print the version of this Pinniped CLI",
}
}

View File

@@ -0,0 +1,85 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here"
)
var (
knownGoodUsageRegexpForVersion = here.Doc(`
Usage:
version \[flags\]
Flags:
-h, --help help for version
`)
knownGoodHelpRegexpForVersion = here.Doc(`
Print the version of this Pinniped CLI
Usage:
version \[flags\]
Flags:
-h, --help help for version
`)
emptyVersionRegexp = `version.Info{Major:"", Minor:"", GitVersion:".*", GitCommit:".*", GitTreeState:"", BuildDate:".*", GoVersion:".*", Compiler:".*", Platform:".*/.*"}`
)
func TestNewVersionCmd(t *testing.T) {
tests := []struct {
name string
args []string
wantError bool
wantStdoutRegexp string
wantStderrRegexp string
}{
{
name: "no flags",
args: []string{},
wantStdoutRegexp: emptyVersionRegexp + "\n",
},
{
name: "help flag passed",
args: []string{"--help"},
wantStdoutRegexp: knownGoodHelpRegexpForVersion,
},
{
name: "arg passed",
args: []string{"tuna"},
wantError: true,
wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
cmd := newVersionCommand()
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)
}
assert.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
assert.Regexp(t, tt.wantStderrRegexp, stderr.String(), "unexpected stderr")
})
}
}

View File

@@ -4,13 +4,13 @@
If you would like to try Pinniped, but you don't have a compatible identity provider,
you can use Pinniped's test identity provider.
See [deploy-local-user-authenticator/README.md](../deploy-local-user-authenticator/README.md)
See [deploy/local-user-authenticator/README.md](../../deploy/local-user-authenticator/README.md)
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.yaml
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
```
## Installing an Older Version with Default Options
@@ -20,20 +20,20 @@ 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.yaml
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2.0/install-pinniped-concierge.yaml
```
## Installing with Custom Options
Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
in the [deploy](../deploy) directory.
in the `deploy/concierge` directory.
Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
1. The configuration options are in [deploy/values.yml](values.yaml).
1. The configuration options are in [deploy/concierge/values.yml](values.yaml).
Fill in the values in that file, or override those values using additional `ytt` command-line options in
the command below. Use the release version tag as the `image_tag` value.
2. In a terminal, cd to this `deploy` directory
2. In a terminal, cd to this `deploy/concierge` directory
3. To generate the final YAML files, run `ytt --file .`
4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
For example: `ytt --file . | kapp deploy --yes --app pinniped --diff-changes --file -`

View File

@@ -0,0 +1,170 @@
---
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: Namespaced
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:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -6,21 +6,18 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: webhookidentityproviders.idp.pinniped.dev
name: webhookauthenticators.authentication.concierge.pinniped.dev
spec:
group: idp.pinniped.dev
group: authentication.concierge.pinniped.dev
names:
categories:
- all
- idp
- idps
kind: WebhookIdentityProvider
listKind: WebhookIdentityProviderList
plural: webhookidentityproviders
shortNames:
- webhookidp
- webhookidps
singular: webhookidentityprovider
- pinniped
- pinniped-authenticator
- pinniped-authenticators
kind: WebhookAuthenticator
listKind: WebhookAuthenticatorList
plural: webhookauthenticators
singular: webhookauthenticator
scope: Namespaced
versions:
- additionalPrinterColumns:
@@ -30,8 +27,8 @@ spec:
name: v1alpha1
schema:
openAPIV3Schema:
description: WebhookIdentityProvider describes the configuration of a Pinniped
webhook identity provider.
description: WebhookAuthenticator describes the configuration of a webhook
authenticator.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@@ -46,7 +43,7 @@ spec:
metadata:
type: object
spec:
description: Spec for configuring the identity provider.
description: Spec for configuring the authenticator.
properties:
endpoint:
description: Webhook server endpoint URL.
@@ -65,11 +62,11 @@ spec:
- endpoint
type: object
status:
description: Status of the identity provider.
description: Status of the authenticator.
properties:
conditions:
description: Represents the observations of an identity provider's
current state.
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

View File

@@ -6,21 +6,22 @@ metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: credentialissuerconfigs.config.pinniped.dev
name: credentialissuers.config.concierge.pinniped.dev
spec:
group: config.pinniped.dev
group: config.concierge.pinniped.dev
names:
kind: CredentialIssuerConfig
listKind: CredentialIssuerConfigList
plural: credentialissuerconfigs
shortNames:
- cic
singular: credentialissuerconfig
categories:
- pinniped
kind: CredentialIssuer
listKind: CredentialIssuerList
plural: credentialissuers
singular: credentialissuer
scope: Namespaced
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

View File

@@ -1,29 +1,32 @@
#! 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", "getAndValidateLogLevel")
#@ if not data.values.into_namespace:
---
apiVersion: v1
kind: Namespace
metadata:
name: #@ data.values.namespace
labels:
name: #@ data.values.namespace
labels: #@ labels()
#@ end
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: #@ data.values.app_name
namespace: #@ data.values.namespace
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
---
apiVersion: v1
kind: ConfigMap
metadata:
name: #@ data.values.app_name + "-config"
namespace: #@ data.values.namespace
labels:
app: #@ data.values.app_name
name: #@ defaultResourceNameWithSuffix("config")
namespace: #@ namespace()
labels: #@ labels()
data:
#! If names.apiService is changed in this ConfigMap, must also change name of the ClusterIP Service resource below.
#@yaml/text-templated-strings
@@ -35,29 +38,36 @@ data:
durationSeconds: (@= str(data.values.api_serving_certificate_duration_seconds) @)
renewBeforeSeconds: (@= str(data.values.api_serving_certificate_renew_before_seconds) @)
names:
servingCertificateSecret: (@= data.values.app_name + "-api-tls-serving-certificate" @)
credentialIssuerConfig: (@= data.values.app_name + "-config" @)
apiService: (@= data.values.app_name + "-api" @)
servingCertificateSecret: (@= defaultResourceNameWithSuffix("api-tls-serving-certificate") @)
credentialIssuer: (@= defaultResourceNameWithSuffix("config") @)
apiService: (@= defaultResourceNameWithSuffix("api") @)
labels: (@= json.encode(labels()).rstrip() @)
kubeCertAgent:
namePrefix: (@= data.values.app_name + "-kube-cert-agent-" @)
namePrefix: (@= defaultResourceNameWithSuffix("kube-cert-agent-") @)
(@ if data.values.kube_cert_agent_image: @)
image: (@= data.values.kube_cert_agent_image @)
(@ else: @)
(@ if data.values.image_digest: @)
image: (@= data.values.image_repo + "@" + data.values.image_digest @)
(@ else: @)
image: (@= data.values.image_repo + ":" + data.values.image_tag @)
(@ end @)
(@ end @)
(@ if data.values.image_pull_dockerconfigjson: @)
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
kind: Secret
metadata:
name: image-pull-secret
namespace: #@ data.values.namespace
labels:
app: #@ data.values.app_name
namespace: #@ namespace()
labels: #@ labels()
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: #@ data.values.image_pull_dockerconfigjson
@@ -66,29 +76,29 @@ data:
apiVersion: apps/v1
kind: Deployment
metadata:
name: #@ data.values.app_name
namespace: #@ data.values.namespace
labels:
app: #@ data.values.app_name
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
spec:
replicas: #@ data.values.replicas
selector:
matchLabels:
app: #@ data.values.app_name
matchLabels: #@ defaultLabel()
template:
metadata:
labels:
app: #@ data.values.app_name
labels: #@ defaultLabel()
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
spec:
serviceAccountName: #@ data.values.app_name
securityContext:
runAsUser: 1001
runAsGroup: 1001
serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:
- name: image-pull-secret
#@ end
containers:
- name: pinniped
- name: #@ defaultResourceName()
#@ if data.values.image_digest:
image: #@ data.values.image_repo + "@" + data.values.image_digest
#@ else:
@@ -97,6 +107,10 @@ spec:
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
args:
- --config=/etc/config/pinniped.yaml
@@ -109,7 +123,7 @@ spec:
livenessProbe:
httpGet:
path: /healthz
port: 443
port: 8443
scheme: HTTPS
initialDelaySeconds: 2
timeoutSeconds: 15
@@ -118,7 +132,7 @@ spec:
readinessProbe:
httpGet:
path: /healthz
port: 443
port: 8443
scheme: HTTPS
initialDelaySeconds: 2
timeoutSeconds: 3
@@ -127,13 +141,16 @@ spec:
volumes:
- name: config-volume
configMap:
name: #@ data.values.app_name + "-config"
name: #@ defaultResourceNameWithSuffix("config")
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "name"
fieldRef:
fieldPath: metadata.name
- path: "namespace"
fieldRef:
fieldPath: metadata.namespace
@@ -153,40 +170,36 @@ spec:
- weight: 50
podAffinityTerm:
labelSelector:
matchLabels:
app: #@ data.values.app_name
matchLabels: #@ defaultLabel()
topologyKey: kubernetes.io/hostname
---
apiVersion: v1
kind: Service
metadata:
#! If name is changed, must also change names.apiService in the ConfigMap above
name: #@ data.values.app_name + "-api"
namespace: #@ data.values.namespace
labels:
app: #@ data.values.app_name
#! If name is changed, must also change names.apiService in the ConfigMap above and spec.service.name in the APIService below.
name: #@ defaultResourceNameWithSuffix("api")
namespace: #@ namespace()
labels: #@ labels()
spec:
type: ClusterIP
selector:
app: #@ data.values.app_name
selector: #@ defaultLabel()
ports:
- protocol: TCP
port: 443
targetPort: 443
targetPort: 8443
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1alpha1.login.pinniped.dev
labels:
app: #@ data.values.app_name
name: v1alpha1.login.concierge.pinniped.dev
labels: #@ labels()
spec:
version: v1alpha1
group: login.pinniped.dev
group: login.concierge.pinniped.dev
groupPriorityMinimum: 2500
versionPriority: 10
#! caBundle: Do not include this key here. Starts out null, will be updated/owned by the golang code.
service:
name: pinniped-api
namespace: #@ data.values.namespace
name: #@ defaultResourceNameWithSuffix("api")
namespace: #@ namespace()
port: 443

View File

@@ -0,0 +1,38 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:template", "template")
#@ def defaultResourceName():
#@ return data.values.app_name
#@ end
#@ def defaultResourceNameWithSuffix(suffix):
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
#@ else:
#@ return data.values.namespace
#@ end
#@ end
#@ def defaultLabel():
app: #@ data.values.app_name
#@ end
#@ def labels():
_: #@ 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

207
deploy/concierge/rbac.yaml Normal file
View File

@@ -0,0 +1,207 @@
#! 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")
#! Give permission to various cluster-scoped objects
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
labels: #@ labels()
rules:
- apiGroups: [ "" ]
resources: [ namespaces ]
verbs: [ get, list, watch ]
- apiGroups: [ apiregistration.k8s.io ]
resources: [ apiservices ]
verbs: [ create, get, list, patch, update, watch ]
- apiGroups: [ admissionregistration.k8s.io ]
resources: [ validatingwebhookconfigurations, mutatingwebhookconfigurations ]
verbs: [ get, list, watch ]
- apiGroups: [ policy ]
resources: [ podsecuritypolicies ]
verbs: [ use ]
- apiGroups: [ security.openshift.io ]
resources: [ securitycontextconstraints ]
verbs: [ use ]
resourceNames: [ nonroot ]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: ClusterRole
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
apiGroup: rbac.authorization.k8s.io
#! Give permission to various objects within the app's own namespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
namespace: #@ namespace()
labels: #@ labels()
rules:
- apiGroups: [ "" ]
resources: [ services ]
verbs: [ create, get, list, patch, update, watch ]
- apiGroups: [ "" ]
resources: [ secrets ]
verbs: [ create, get, list, patch, update, watch, delete ]
#! We need to be able to CRUD pods in our namespace so we can reconcile the kube-cert-agent pods.
- apiGroups: [ "" ]
resources: [ pods ]
verbs: [ create, get, list, patch, update, watch, delete ]
#! We need to be able to exec into pods in our namespace so we can grab the API server's private key
- 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
metadata:
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
namespace: #@ namespace()
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: Role
name: #@ defaultResourceNameWithSuffix("aggregated-api-server")
apiGroup: rbac.authorization.k8s.io
#! Give permission to read pods in the kube-system namespace so we can find the API server's private key
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
namespace: kube-system
labels: #@ labels()
rules:
- apiGroups: [ "" ]
resources: [ pods ]
verbs: [ get, list, watch ]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
namespace: kube-system
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: Role
name: #@ defaultResourceNameWithSuffix("kube-system-pod-read")
apiGroup: rbac.authorization.k8s.io
#! Allow both authenticated and unauthenticated TokenCredentialRequests (i.e. allow all requests)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
labels: #@ labels()
rules:
- apiGroups: [ login.concierge.pinniped.dev ]
resources: [ tokencredentialrequests ]
verbs: [ create ]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
labels: #@ labels()
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: #@ defaultResourceNameWithSuffix("create-token-credential-requests")
apiGroup: rbac.authorization.k8s.io
#! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceName()
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: ClusterRole
name: system:auth-delegator
apiGroup: rbac.authorization.k8s.io
#! Give permissions for a special configmap of CA bundles that is needed by aggregated api servers
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("extension-apiserver-authentication-reader")
namespace: kube-system
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: Role
name: extension-apiserver-authentication-reader
apiGroup: rbac.authorization.k8s.io
#! Give permission to list and watch ConfigMaps in kube-public
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
namespace: kube-public
labels: #@ labels()
rules:
- apiGroups: [ "" ]
resources: [ configmaps ]
verbs: [ list, watch ]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
namespace: kube-public
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: Role
name: #@ defaultResourceNameWithSuffix("cluster-info-lister-watcher")
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,56 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
---
app_name: pinniped-concierge
#! Creates a new namespace statically in yaml with the given name and installs the app into that namespace.
namespace: pinniped-concierge
#! If specified, assumes that a namespace of the given name already exists and installs the app into that namespace.
#! If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used.
into_namespace: #! e.g. my-preexisting-namespace
#! All resources created statically by yaml at install-time and all resources created dynamically
#! by controllers at runtime will be labelled with `app: $app_name` and also with the labels
#! specified here. The value of `custom_labels` must be a map of string keys to string values.
#! The app can be uninstalled either by:
#! 1. Deleting the static install-time yaml resources including the static namespace, which will cascade and also delete
#! resources that were dynamically created by controllers at runtime
#! 2. Or, deleting all resources by label, which does not assume that there was a static install-time yaml namespace.
custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLabelName: otherCustomLabelValue}
#! Specify how many replicas of the Pinniped server to run.
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
#! Optionally specify a different image for the "kube-cert-agent" pod which is scheduled
#! on the control plane. This image needs only to include `sleep` and `cat` binaries.
#! By default, the same image specified for image_repo/image_digest/image_tag will be re-used.
kube_cert_agent_image:
#! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry.
#! 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"}}}
#! Pinniped will try to guess the right K8s API URL for sharing that information with potential clients.
#! This settings allows the guess to be overridden.
#! Optional.
discovery_url: #! e.g., https://example.com
#! Specify the duration and renewal interval for the API serving certificate.
#! The defaults are set to expire the cert about every 30 days, and to rotate it
#! 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.

View File

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

View File

@@ -15,7 +15,7 @@ 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/download/$(curl https://api.github.com/repos/vmware-tanzu/pinniped/releases/latest -s | jq .name -r)/install-local-user-authenticator.yaml
kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/latest/download/install-local-user-authenticator.yaml
```
## Installing an Older Version with Default Options
@@ -31,14 +31,14 @@ kubectl apply -f https://github.com/vmware-tanzu/pinniped/releases/download/v0.2
## Installing with Custom Options
Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
in the [deploy-local-user-authenticator](../deploy-local-user-authenticator) directory.
in the `deploy/local-user-authenticator` directory.
Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
1. The configuration options are in [deploy-local-user-authenticator/values.yml](values.yaml).
1. The configuration options are in [deploy/local-user-authenticator/values.yml](values.yaml).
Fill in the values in that file, or override those values using additional `ytt` command-line options in
the command below. Use the release version tag as the `image_tag` value.
2. In a terminal, cd to this `deploy-local-user-authenticator` directory
2. In a terminal, cd to this `deploy/local-user-authenticator` directory
3. To generate the final YAML files, run `ytt --file .`
4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
For example: `ytt --file . | kapp deploy --yes --app local-user-authenticator --diff-changes --file -`
@@ -50,11 +50,11 @@ Either [install `ytt`](https://get-ytt.io/) or use the [container image from Doc
Use `kubectl` to create, edit, and delete user accounts by creating a `Secret` for each user account in the same
namespace where local-user-authenticator is deployed. The name of the `Secret` resource is the username.
Store the user's group membership and `bcrypt` encrypted password as the contents of the `Secret`.
For example, to create a user named `ryan` with the password `password123`
For example, to create a user named `pinny-the-seal` with the password `password123`
who belongs to the groups `group1` and `group2`, use:
```bash
kubectl create secret generic ryan \
kubectl create secret generic pinny-the-seal \
--namespace local-user-authenticator \
--from-literal=groups=group1,group2 \
--from-literal=passwordHash=$(htpasswd -nbBC 10 x password123 | sed -e "s/^x://")
@@ -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
@@ -109,7 +109,7 @@ is configured as an identity provider for Pinniped.
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
"token": "ryan:password123"
"token": "pinny-the-seal:password123"
}
}'
```
@@ -118,14 +118,42 @@ is configured as an identity provider for Pinniped.
Note that the value of `authenticated` is `true` to indicate a successful authentication.
```json
{"apiVersion":"authentication.k8s.io/v1beta1","kind":"TokenReview","status":{"authenticated":true,"user":{"username":"ryan","uid":"19c433ec-8f58-44ca-9ef0-2d1081ccb876","groups":["group1","group2"]}}}
{
"kind": "TokenReview",
"apiVersion": "authentication.k8s.io/v1beta1",
"metadata": {
"creationTimestamp": null
},
"spec": {},
"status": {
"authenticated": true,
"user": {
"username": "pinny-the-seal",
"uid": "19c433ec-8f58-44ca-9ef0-2d1081ccb876",
"groups": [
"group1",
"group2"
]
}
}
}
```
Trying the above `curl` command again with the wrong username or password in the body of the request
should result in a JSON response which indicates that the authentication failed.
```json
{"apiVersion":"authentication.k8s.io/v1beta1","kind":"TokenReview","status":{"authenticated":false}}
{
"kind": "TokenReview",
"apiVersion": "authentication.k8s.io/v1beta1",
"metadata": {
"creationTimestamp": null
},
"spec": {},
"status": {
"user": {}
}
}
```
1. Remove the curl pod.

View File

@@ -47,6 +47,9 @@ spec:
labels:
app: local-user-authenticator
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
serviceAccountName: local-user-authenticator
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:
@@ -77,4 +80,4 @@ spec:
ports:
- protocol: TCP
port: 443
targetPort: 443
targetPort: 8443

View File

@@ -5,11 +5,11 @@
---
#! 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
#! Specifies a secret to be used when pulling the above container image.
#! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry.
#! 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.

View File

@@ -1,185 +0,0 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#! Give permission to various cluster-scoped objects
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: #@ data.values.app_name + "-aggregated-api-server"
rules:
- apiGroups: [""]
resources: [namespaces]
verbs: [get, list, watch]
- apiGroups: [apiregistration.k8s.io]
resources: [apiservices]
verbs: [create, get, list, patch, update, watch]
- apiGroups: [admissionregistration.k8s.io]
resources: [validatingwebhookconfigurations, mutatingwebhookconfigurations]
verbs: [get, list, watch]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-aggregated-api-server"
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: ClusterRole
name: #@ data.values.app_name + "-aggregated-api-server"
apiGroup: rbac.authorization.k8s.io
#! Give permission to various objects within the app's own namespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: #@ data.values.app_name + "-aggregated-api-server"
namespace: #@ data.values.namespace
rules:
- apiGroups: [""]
resources: [services]
verbs: [create, get, list, patch, update, watch]
- apiGroups: [""]
resources: [secrets]
verbs: [create, get, list, patch, update, watch, delete]
#! We need to be able to CRUD pods in our namespace so we can reconcile the kube-cert-agent pods.
- apiGroups: [""]
resources: [pods]
verbs: [create, get, list, patch, update, watch, delete]
#! We need to be able to exec into pods in our namespace so we can grab the API server's private key
- apiGroups: [""]
resources: [pods/exec]
verbs: [create]
- apiGroups: [config.pinniped.dev, idp.pinniped.dev]
resources: ["*"]
verbs: [create, get, list, update, watch]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-aggregated-api-server"
namespace: #@ data.values.namespace
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: Role
name: #@ data.values.app_name + "-aggregated-api-server"
apiGroup: rbac.authorization.k8s.io
#! Give permission to read pods in the kube-system namespace so we can find the API server's private key
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: #@ data.values.app_name + "-kube-system-pod-read"
namespace: kube-system
rules:
- apiGroups: [""]
resources: [pods]
verbs: [get, list, watch]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-kube-system-pod-read"
namespace: kube-system
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: Role
name: #@ data.values.app_name + "-kube-system-pod-read"
apiGroup: rbac.authorization.k8s.io
#! Allow both authenticated and unauthenticated TokenCredentialRequests (i.e. allow all requests)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: #@ data.values.app_name + "-create-token-credential-requests"
rules:
- apiGroups: [login.pinniped.dev]
resources: [tokencredentialrequests]
verbs: [create]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-create-token-credential-requests"
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: system:unauthenticated
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: #@ data.values.app_name + "-create-token-credential-requests"
apiGroup: rbac.authorization.k8s.io
#! Give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name
namespace: #@ data.values.namespace
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: ClusterRole
name: system:auth-delegator
apiGroup: rbac.authorization.k8s.io
#! Give permissions for a special configmap of CA bundles that is needed by aggregated api servers
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-extension-apiserver-authentication-reader"
namespace: kube-system
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: Role
name: extension-apiserver-authentication-reader
apiGroup: rbac.authorization.k8s.io
#! Give permission to list and watch ConfigMaps in kube-public
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-cluster-info-lister-watcher"
namespace: kube-public
rules:
- apiGroups: [""]
resources: [configmaps]
verbs: [list, watch]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: #@ data.values.app_name + "-cluster-info-lister-watcher"
namespace: kube-public
subjects:
- kind: ServiceAccount
name: #@ data.values.app_name
namespace: #@ data.values.namespace
roleRef:
kind: Role
name: #@ data.values.app_name + "-cluster-info-lister-watcher"
apiGroup: rbac.authorization.k8s.io

184
deploy/supervisor/README.md Normal file
View File

@@ -0,0 +1,184 @@
# Deploying the Pinniped Supervisor
## What is the Pinniped Supervisor?
The Pinniped Supervisor app is a component of the Pinniped OIDC and Cluster Federation solutions.
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
```
## Installing an Older 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
```
## Installing with Custom Options
Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
in the `deploy/supervisor` directory.
Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
1. The configuration options are in [deploy/supervisor/values.yml](values.yaml).
Fill in the values in that file, or override those values using additional `ytt` command-line options in
the command below. Use the release version tag as the `image_tag` value.
2. In a terminal, cd to this `deploy/supervisor` directory
3. To generate the final YAML files, run `ytt --file .`
4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
For example: `ytt --file . | kapp deploy --yes --app pinniped-supervisor --diff-changes --file -`
## Configuring After Installing
### Exposing the Supervisor App as a Service
The Supervisor app's endpoints should be exposed as HTTPS endpoints with proper TLS certificates signed by a
Certificate Authority which will be trusted by your user's web browsers. Because there are
many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
The most common ways are:
1. Define an [`Ingress` resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) with TLS certificates.
In this case, the ingress will terminate TLS. Typically, the ingress will then talk plain HTTP to its backend,
which would be a NodePort or LoadBalancer Service in front of the HTTP port 8080 of the Supervisor pods.
The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the
documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably
want to start with that provider's documentation. For example, if your cluster is a Google GKE cluster, refer to
the [GKE documentation for Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress).
Otherwise, the Kubernetes documentation provides a list of popular
[Ingress Controllers](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), including
[Contour](https://projectcontour.io/) and many others.
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 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
to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption.
1. Or, expose the Supervisor app using a Kubernetes service mesh technology, e.g. [Istio](https://istio.io/).
Please see the documentation for your service mesh. Generally, the setup would be similar to the description
above for defining an ingress, expect the service mesh would probably provide both the ingress with TLS termination
and the service.
For either of the first two options mentioned above, if you installed using `ytt` then you can use
the related `service_*` options from [deploy/supervisor/values.yml](values.yaml) to create a Service.
If you installed using `install-supervisor.yaml` then you can create
the Service separately after installing the Supervisor app. There is no `Ingress` included in the `ytt` templates,
so if you choose to use an Ingress then you'll need to create that separately after installing the Supervisor app.
#### Example: Using a LoadBalancer Service
This is an example of creating a LoadBalancer Service to expose port 8443 of the Supervisor app outside the cluster.
```yaml
apiVersion: v1
kind: Service
metadata:
name: pinniped-supervisor-loadbalancer
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
namespace: pinniped-supervisor
spec:
type: LoadBalancer
selector:
# Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
app: pinniped-supervisor
ports:
- protocol: TCP
port: 443
targetPort: 8443
```
#### Example: Using a NodePort Service
A NodePort Service exposes the app as a port on the nodes of the cluster.
This is convenient for use with kind clusters, because kind can
[expose node ports as localhost ports on the host machine](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings)
without requiring an Ingress, although
[kind also supports several Ingress Controllers](https://kind.sigs.k8s.io/docs/user/ingress).
A NodePort Service could also be used behind an Ingress which is terminating TLS.
For example:
```yaml
apiVersion: v1
kind: Service
metadata:
name: pinniped-supervisor-nodeport
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
namespace: pinniped-supervisor
spec:
type: NodePort
selector:
# Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
app: pinniped-supervisor
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 31234 # This is the port that you would forward to the kind host. Or omit this key for a random port.
```
### Configuring the Supervisor to Act as an OIDC Provider
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: FederationDomain
metadata:
name: my-provider
# Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
namespace: pinniped-supervisor
spec:
# The hostname would typically match the DNS name of the public ingress or load balancer for the cluster.
# Any path can be specified, which allows a single hostname to have multiple different issuers. The path is optional.
issuer: https://my-issuer.example.com/any/path
# Optionally configure the 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 OIDC Provider.
tls:
secretName: my-tls-cert-secret
```
#### 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 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 `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.
The default TLS certificate will be used for all OIDC providers which did not declare a `spec.tls.secretName`.
Also, the `spec.tls.secretName` will be ignored for incoming requests to the OIDC endpoints
that use an IP address as the host, so those requests will always present the default TLS certificates
to the client. When the request includes the hostname, and that hostname matches the hostname of an `Issuer`,
then the TLS certificate defined by the `spec.tls.secretName` will be used. If that issuer did not
define `spec.tls.secretName` then the default TLS certificate will be used. If neither exists,
then the client will get a TLS error because the server will not present any TLS certificate.
It is recommended that you have a DNS entry for your load balancer or Ingress, and that you configure the
OIDC provider's `Issuer` using that DNS hostname, and that the TLS certificate for that provider also
covers that same hostname.
You can create the certificate Secrets however you like, for example you could use [cert-manager](https://cert-manager.io/)
or `kubectl create secret tls`.
Keep in mind that your users will load some of these endpoints in their web browsers, so the TLS certificates
should be signed by a Certificate Authority that will be trusted by their browsers.

View File

@@ -0,0 +1,158 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
creationTimestamp: null
name: federationdomains.config.supervisor.pinniped.dev
spec:
group: config.supervisor.pinniped.dev
names:
categories:
- pinniped
kind: FederationDomain
listKind: FederationDomainList
plural: federationdomains
singular: federationdomain
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: FederationDomain describes the configuration of an OIDC 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 of the OIDC provider.
properties:
issuer:
description: "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).
\n See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3
for more information."
minLength: 1
type: string
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 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
(TLS) supported by all major browsers. \n 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. \n
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. \n When your Issuer URL's host
is an IP address, then this field is ignored. SNI does not work
for IP addresses."
type: string
type: object
required:
- issuer
type: object
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: 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).
format: date-time
type: string
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.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@@ -0,0 +1,147 @@
#! Copyright 2020 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", "getAndValidateLogLevel")
#@ if not data.values.into_namespace:
---
apiVersion: v1
kind: Namespace
metadata:
name: #@ data.values.namespace
labels: #@ labels()
#@ end
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
---
apiVersion: v1
kind: ConfigMap
metadata:
name: #@ defaultResourceNameWithSuffix("static-config")
namespace: #@ namespace()
labels: #@ labels()
data:
#@yaml/text-templated-strings
pinniped.yaml: |
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
kind: Secret
metadata:
name: image-pull-secret
namespace: #@ namespace()
labels: #@ labels()
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: #@ data.values.image_pull_dockerconfigjson
#@ end
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
spec:
replicas: #@ data.values.replicas
selector:
matchLabels: #@ defaultLabel()
template:
metadata:
labels: #@ defaultLabel()
spec:
securityContext:
runAsUser: 1001
runAsGroup: 1001
serviceAccountName: #@ defaultResourceName()
#@ if data.values.image_pull_dockerconfigjson and data.values.image_pull_dockerconfigjson != "":
imagePullSecrets:
- name: image-pull-secret
#@ end
containers:
- name: #@ defaultResourceName()
#@ if data.values.image_digest:
image: #@ data.values.image_repo + "@" + data.values.image_digest
#@ else:
image: #@ data.values.image_repo + ":" + data.values.image_tag
#@ end
imagePullPolicy: IfNotPresent
command: #! override the default entrypoint
- /usr/local/bin/pinniped-supervisor
args:
- /etc/podinfo
- /etc/config/pinniped.yaml
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "100m"
memory: "128Mi"
volumeMounts:
- name: config-volume
mountPath: /etc/config
- name: podinfo
mountPath: /etc/podinfo
ports:
- containerPort: 8080
protocol: TCP
- containerPort: 8443
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 2
timeoutSeconds: 15
periodSeconds: 10
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 2
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 3
volumes:
- name: config-volume
configMap:
name: #@ defaultResourceNameWithSuffix("static-config")
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- 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:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
podAffinityTerm:
labelSelector:
matchLabels: #@ defaultLabel()
topologyKey: kubernetes.io/hostname

View File

@@ -0,0 +1,38 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("@ytt:template", "template")
#@ def defaultResourceName():
#@ return data.values.app_name
#@ end
#@ def defaultResourceNameWithSuffix(suffix):
#@ return data.values.app_name + "-" + suffix
#@ end
#@ def namespace():
#@ if data.values.into_namespace:
#@ return data.values.into_namespace
#@ else:
#@ return data.values.namespace
#@ end
#@ end
#@ def defaultLabel():
app: #@ data.values.app_name
#@ end
#@ def labels():
_: #@ 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

@@ -0,0 +1,50 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#! Give permission to various objects within the app's own namespace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
rules:
- apiGroups: [""]
resources: [secrets]
verbs: [create, get, list, patch, update, watch, delete]
- apiGroups: [config.supervisor.pinniped.dev]
resources: [federationdomains]
verbs: [update, get, list, watch]
- apiGroups: [idp.supervisor.pinniped.dev]
resources: [oidcidentityproviders]
verbs: [get, list, watch]
- apiGroups: [idp.supervisor.pinniped.dev]
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
metadata:
name: #@ defaultResourceName()
namespace: #@ namespace()
labels: #@ labels()
subjects:
- kind: ServiceAccount
name: #@ defaultResourceName()
namespace: #@ namespace()
roleRef:
kind: Role
name: #@ defaultResourceName()
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,93 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data")
#@ load("helpers.lib.yaml", "defaultLabel", "labels", "namespace", "defaultResourceName", "defaultResourceNameWithSuffix")
#@ if data.values.service_http_nodeport_port or data.values.service_https_nodeport_port:
---
apiVersion: v1
kind: Service
metadata:
name: #@ defaultResourceNameWithSuffix("nodeport")
namespace: #@ namespace()
labels: #@ labels()
spec:
type: NodePort
selector:
app: #@ data.values.app_name
ports:
#@ if data.values.service_http_nodeport_port:
- name: http
protocol: TCP
port: #@ data.values.service_http_nodeport_port
targetPort: 8080
#@ if data.values.service_http_nodeport_nodeport:
nodePort: #@ data.values.service_http_nodeport_nodeport
#@ end
#@ end
#@ if data.values.service_https_nodeport_port:
- name: https
protocol: TCP
port: #@ data.values.service_https_nodeport_port
targetPort: 8443
#@ if data.values.service_https_nodeport_nodeport:
nodePort: #@ data.values.service_https_nodeport_nodeport
#@ end
#@ end
#@ end
#@ if data.values.service_http_clusterip_port or data.values.service_https_clusterip_port:
---
apiVersion: v1
kind: Service
metadata:
name: #@ defaultResourceNameWithSuffix("clusterip")
namespace: #@ namespace()
labels: #@ labels()
spec:
type: ClusterIP
selector: #@ defaultLabel()
ports:
#@ if data.values.service_http_clusterip_port:
- name: http
protocol: TCP
port: #@ data.values.service_http_clusterip_port
targetPort: 8080
#@ end
#@ if data.values.service_https_clusterip_port:
- name: https
protocol: TCP
port: #@ data.values.service_https_clusterip_port
targetPort: 8443
#@ end
#@ end
#@ if data.values.service_http_loadbalancer_port or data.values.service_https_loadbalancer_port:
---
apiVersion: v1
kind: Service
metadata:
name: #@ defaultResourceNameWithSuffix("loadbalancer")
namespace: #@ namespace()
labels: #@ labels()
spec:
type: LoadBalancer
selector: #@ defaultLabel()
#@ if data.values.service_loadbalancer_ip:
loadBalancerIP: #@ data.values.service_loadbalancer_ip
#@ end
ports:
#@ if data.values.service_http_loadbalancer_port:
- name: http
protocol: TCP
port: #@ data.values.service_http_loadbalancer_port
targetPort: 8080
#@ end
#@ if data.values.service_https_loadbalancer_port:
- name: https
protocol: TCP
port: #@ data.values.service_https_loadbalancer_port
targetPort: 8443
#@ end
#@ end

View File

@@ -0,0 +1,58 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
---
app_name: pinniped-supervisor
#! Creates a new namespace statically in yaml with the given name and installs the app into that namespace.
namespace: pinniped-supervisor
#! If specified, assumes that a namespace of the given name already exists and installs the app into that namespace.
#! If both `namespace` and `into_namespace` are specified, then only `into_namespace` is used.
into_namespace: #! e.g. my-preexisting-namespace
#! All resources created statically by yaml at install-time and all resources created dynamically
#! by controllers at runtime will be labelled with `app: $app_name` and also with the labels
#! specified here. The value of `custom_labels` must be a map of string keys to string values.
#! The app can be uninstalled either by:
#! 1. Deleting the static install-time yaml resources including the static namespace, which will cascade and also delete
#! resources that were dynamically created by controllers at runtime
#! 2. Or, deleting all resources by label, which does not assume that there was a static install-time yaml namespace.
custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLabelName: otherCustomLabelValue}
#! Specify how many replicas of the Pinniped server to run.
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
#! Specifies a secret to be used when pulling the above `image_repo` container image.
#! Can be used when the above image_repo is a private registry.
#! 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"}}}
#! Specify how to expose the Supervisor app's HTTP and/or HTTPS ports as a Service.
#! Typically you would set a value for only one of the following service types, for either HTTP or HTTPS depending on your needs.
#! An HTTP service should not be exposed outside the cluster. It would not be secure to serve OIDC endpoints to end users via HTTP.
#! Setting any of these values means that a Service of that type will be created.
#! Note that all port numbers should be numbers (not strings), i.e. use ytt's `--data-value-yaml` instead of `--data-value`.
service_http_nodeport_port: #! when specified, creates a NodePort Service with this `port` value, with port 8080 as its `targetPort`; e.g. 31234
service_http_nodeport_nodeport: #! the `nodePort` value of the NodePort Service, optional when `service_http_nodeport_port` is specified; e.g. 31234
service_http_loadbalancer_port: #! when specified, creates a LoadBalancer Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
service_http_clusterip_port: #! when specified, creates a ClusterIP Service with this `port` value, with port 8080 as its `targetPort`; e.g. 8443
service_https_nodeport_port: #! when specified, creates a NodePort Service with this `port` value, with port 8443 as its `targetPort`; e.g. 31243
service_https_nodeport_nodeport: #! the `nodePort` value of the NodePort Service, optional when `service_http_nodeport_port` is specified; e.g. 31243
service_https_loadbalancer_port: #! when specified, creates a LoadBalancer Service with this `port` value, with port 8443 as its `targetPort`; e.g. 8443
service_https_clusterip_port: #! when specified, creates a ClusterIP Service with this `port` value, with port 8443 as its `targetPort`; e.g. 8443
#! The `loadBalancerIP` value of the LoadBalancer Service.
#! 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.

View File

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

View File

@@ -1,33 +0,0 @@
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0
#@data/values
---
app_name: pinniped
namespace: pinniped
#! Specify how many replicas of the Pinniped server to run.
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_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest
#! Specifies a secret to be used when pulling the above container image.
#! Can be used when the above image_repo is a private registry.
#! 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"}}}
#! Pinniped will try to guess the right K8s API URL for sharing that information with potential clients.
#! This settings allows the guess to be overridden.
#! Optional.
discovery_url: #! e.g., https://example.com
#! Specify the duration and renewal interval for the API serving certificate.
#! The defaults are set to expire the cert about every 30 days, and to rotate it
#! about every 25 days.
api_serving_certificate_duration_seconds: 2592000
api_serving_certificate_renew_before_seconds: 2160000

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

@@ -5,112 +5,29 @@
== API Reference
.Packages
- xref:{anchor_prefix}-config-pinniped-dev-v1alpha1[$$config.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-idp-pinniped-dev-v1alpha1[$$idp.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-login-pinniped-dev-v1alpha1[$$login.pinniped.dev/v1alpha1$$]
- xref:{anchor_prefix}-authentication-concierge-pinniped-dev-v1alpha1[$$authentication.concierge.pinniped.dev/v1alpha1$$]
- 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$$]
[id="{anchor_prefix}-config-pinniped-dev-v1alpha1"]
=== config.pinniped.dev/v1alpha1
[id="{anchor_prefix}-authentication-concierge-pinniped-dev-v1alpha1"]
=== authentication.concierge.pinniped.dev/v1alpha1
Package v1alpha1 is the v1alpha1 version of the Pinniped configuration API.
Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authentication API.
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfig"]
==== CredentialIssuerConfig
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfiglist[$$CredentialIssuerConfigList$$]
****
[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`.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstatus[$$CredentialIssuerConfigStatus$$]__ | Status of the credential issuer.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigkubeconfiginfo"]
==== CredentialIssuerConfigKubeConfigInfo
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstatus[$$CredentialIssuerConfigStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`server`* __string__ | The K8s API server URL.
| *`certificateAuthorityData`* __string__ | The K8s API server CA bundle.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstatus"]
==== CredentialIssuerConfigStatus
Status of a credential issuer.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfig[$$CredentialIssuerConfig$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`strategies`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstrategy[$$CredentialIssuerConfigStrategy$$] array__ | List of integration strategies that were attempted by Pinniped.
| *`kubeConfigInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigkubeconfiginfo[$$CredentialIssuerConfigKubeConfigInfo$$]__ | Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstrategy"]
==== CredentialIssuerConfigStrategy
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-config-v1alpha1-credentialissuerconfigstatus[$$CredentialIssuerConfigStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`type`* __StrategyType__ | Type of integration attempted.
| *`status`* __StrategyStatus__ | Status of the attempted integration strategy.
| *`reason`* __StrategyReason__ | Reason for the current status.
| *`message`* __string__ | Human-readable description of the current status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | When the status was last checked.
|===
[id="{anchor_prefix}-idp-pinniped-dev-v1alpha1"]
=== idp.pinniped.dev/v1alpha1
Package v1alpha1 is the v1alpha1 version of the Pinniped identity provider API.
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-condition"]
[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-idp-v1alpha1-webhookidentityproviderstatus[$$WebhookIdentityProviderStatus$$]
- 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$$]
****
[cols="25a,75a", options="header"]
@@ -125,14 +42,105 @@ Condition status of a resource (mirrored from the metav1.Condition type added in
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-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 identity providers.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityproviderspec[$$WebhookIdentityProviderSpec$$]
- 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$$]
****
[cols="25a,75a", options="header"]
@@ -142,14 +150,14 @@ Configuration for configuring TLS on various identity providers.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityprovider"]
==== WebhookIdentityProvider
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticator"]
==== WebhookAuthenticator
WebhookIdentityProvider describes the configuration of a Pinniped webhook identity provider.
WebhookAuthenticator describes the configuration of a webhook authenticator.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityproviderlist[$$WebhookIdentityProviderList$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorlist[$$WebhookAuthenticatorList$$]
****
[cols="25a,75a", options="header"]
@@ -157,64 +165,420 @@ WebhookIdentityProvider describes the configuration of a Pinniped webhook identi
| 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-idp-v1alpha1-webhookidentityproviderspec[$$WebhookIdentityProviderSpec$$]__ | Spec for configuring the identity provider.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityproviderstatus[$$WebhookIdentityProviderStatus$$]__ | Status of the identity provider.
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec[$$WebhookAuthenticatorSpec$$]__ | Spec for configuring the authenticator.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]__ | Status of the authenticator.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityproviderspec"]
==== WebhookIdentityProviderSpec
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
==== WebhookAuthenticatorSpec
Spec for configuring a webhook identity provider.
Spec for configuring a webhook authenticator.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityprovider[$$WebhookIdentityProvider$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticator[$$WebhookAuthenticator$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`endpoint`* __string__ | Webhook server endpoint URL.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration.
| *`tls`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-tlsspec[$$TLSSpec$$]__ | TLS configuration.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityproviderstatus"]
==== WebhookIdentityProviderStatus
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus"]
==== WebhookAuthenticatorStatus
Status of a webhook identity provider.
Status of a webhook authenticator.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-webhookidentityprovider[$$WebhookIdentityProvider$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-authentication-v1alpha1-webhookauthenticator[$$WebhookAuthenticator$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-idp-v1alpha1-condition[$$Condition$$]__ | Represents the observations of an identity provider'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}-login-pinniped-dev-v1alpha1"]
=== login.pinniped.dev/v1alpha1
[id="{anchor_prefix}-config-concierge-pinniped-dev-v1alpha1"]
=== config.concierge.pinniped.dev/v1alpha1
Package v1alpha1 is the v1alpha1 version of the Pinniped concierge configuration API.
[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:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerlist[$$CredentialIssuerList$$]
****
[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`.
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]__ | Status of the credential issuer.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo"]
==== CredentialIssuerKubeConfigInfo
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`server`* __string__ | The K8s API server URL.
| *`certificateAuthorityData`* __string__ | The K8s API server CA bundle.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus"]
==== CredentialIssuerStatus
Status of a credential issuer.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuer[$$CredentialIssuer$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`strategies`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstrategy[$$CredentialIssuerStrategy$$] array__ | List of integration strategies that were attempted by Pinniped.
| *`kubeConfigInfo`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerkubeconfiginfo[$$CredentialIssuerKubeConfigInfo$$]__ | Information needed to form a valid Pinniped-based kubeconfig using this credential issuer.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstrategy"]
==== CredentialIssuerStrategy
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-config-v1alpha1-credentialissuerstatus[$$CredentialIssuerStatus$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`type`* __StrategyType__ | Type of integration attempted.
| *`status`* __StrategyStatus__ | Status of the attempted integration strategy.
| *`reason`* __StrategyReason__ | Reason for the current status.
| *`message`* __string__ | Human-readable description of the current status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#time-v1-meta[$$Time$$]__ | When the status was last checked.
|===
[id="{anchor_prefix}-config-supervisor-pinniped-dev-v1alpha1"]
=== config.supervisor.pinniped.dev/v1alpha1
Package v1alpha1 is the v1alpha1 version of the Pinniped supervisor configuration API.
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomain"]
==== FederationDomain
FederationDomain describes the configuration of an OIDC provider.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-supervisor-config-v1alpha1-federationdomainlist[$$FederationDomainList$$]
****
[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-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-federationdomainsecrets"]
==== FederationDomainSecrets
FederationDomainSecrets holds information about this OIDC Provider's secrets.
.Appears In:
****
- 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"]
|===
| 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-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-federationdomainstatus"]
==== FederationDomainStatus
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-federationdomain[$$FederationDomain$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`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).
| *`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-federationdomaintlsspec"]
==== FederationDomainTLSSpec
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-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 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.
When your Issuer URL's host is an IP address, then this field is ignored. SNI does not work for IP addresses.
|===
[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
Package v1alpha1 is the v1alpha1 version of the Pinniped login API.
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-clustercredential"]
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-clustercredential"]
==== ClusterCredential
ClusterCredential is the cluster-specific credential returned on a successful credential request. It contains either a valid bearer token or a valid TLS certificate and corresponding private key for the cluster.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequeststatus[$$TokenCredentialRequestStatus$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequeststatus[$$TokenCredentialRequestStatus$$]
****
[cols="25a,75a", options="header"]
@@ -227,14 +591,14 @@ ClusterCredential is the cluster-specific credential returned on a successful cr
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequest"]
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequest"]
==== TokenCredentialRequest
TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchange for a cluster-specific credential.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequestlist[$$TokenCredentialRequestList$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequestlist[$$TokenCredentialRequestList$$]
****
[cols="25a,75a", options="header"]
@@ -242,45 +606,45 @@ TokenCredentialRequest submits an IDP-specific credential to Pinniped in exchang
| 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-login-v1alpha1-tokencredentialrequestspec[$$TokenCredentialRequestSpec$$]__ |
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequeststatus[$$TokenCredentialRequestStatus$$]__ |
| *`spec`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequestspec[$$TokenCredentialRequestSpec$$]__ |
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequeststatus[$$TokenCredentialRequestStatus$$]__ |
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequestspec"]
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequestspec"]
==== TokenCredentialRequestSpec
TokenCredentialRequestSpec is the specification of a TokenCredentialRequest, expected on requests to the Pinniped API.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequest[$$TokenCredentialRequest$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequest[$$TokenCredentialRequest$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`token`* __string__ | Bearer token supplied with the credential request.
| *`identityProvider`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | Reference to an identity provider which can fulfill this credential request.
| *`authenticator`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#typedlocalobjectreference-v1-core[$$TypedLocalObjectReference$$]__ | Reference to an authenticator which can validate this credential request.
|===
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequeststatus"]
[id="{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequeststatus"]
==== TokenCredentialRequestStatus
TokenCredentialRequestStatus is the status of a TokenCredentialRequest, returned on responses to the Pinniped API.
.Appears In:
****
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-tokencredentialrequest[$$TokenCredentialRequest$$]
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-tokencredentialrequest[$$TokenCredentialRequest$$]
****
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`credential`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-login-v1alpha1-clustercredential[$$ClusterCredential$$]__ | A Credential will be returned for a successful credential request.
| *`credential`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-17-apis-concierge-login-v1alpha1-clustercredential[$$ClusterCredential$$]__ | A Credential will be returned for a successful credential request.
| *`message`* __string__ | An error message will be returned for an unsuccessful credential request.
|===

View File

@@ -0,0 +1,10 @@
// 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=authentication.concierge.pinniped.dev
// Package v1alpha1 is the v1alpha1 version of the Pinniped concierge authentication API.
package v1alpha1

View File

@@ -9,7 +9,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "config.pinniped.dev"
const GroupName = "authentication.concierge.pinniped.dev"
// SchemeGroupVersion is group version used to register these objects.
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
@@ -24,14 +24,16 @@ 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,
&CredentialIssuerConfig{},
&CredentialIssuerConfigList{},
&WebhookAuthenticator{},
&WebhookAuthenticatorList{},
&JWTAuthenticator{},
&JWTAuthenticatorList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil

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