Compare commits

..

7 Commits

Author SHA1 Message Date
Joshua Casey
c6112ad3a9 wip 2 2023-01-11 21:24:03 -06:00
Joshua Casey
bfe8dc11ce wip 2023-01-11 17:45:16 -06:00
Joshua Casey
d7b5f4d4ea Clarify godoc for AdditionalClaimMappings to indicate that missing upstream claims will not show up in the downstream token 2023-01-11 12:58:10 -06:00
Joshua Casey
7b01c3ce18 Run 'go generate ./...' 2023-01-11 12:31:48 -06:00
Joshua Casey
87fe42e18d Address review feedback 2023-01-09 09:18:17 -06:00
Joshua Casey
02ed2d9c95 Backfill tests 2023-01-04 13:29:14 -06:00
Ryan Richard
cd17bdb5f7 POC of OIDCIdentityProvider.spec.claims.additionalClaimMappings
Proof-of-concept implementation of a potential new Supervisor feature
which allows arbitrary upstream ID token claims to be mapped into a
new top-level claim in the ID tokens issued by the Supervisor.
2022-12-21 07:13:26 -06:00
2044 changed files with 53373 additions and 48814 deletions

View File

@ -21,6 +21,3 @@
# MacOS Desktop Services Store # MacOS Desktop Services Store
.DS_Store .DS_Store
# Hugo temp file
.hugo_build.lock

View File

@ -1,19 +0,0 @@
kind: pipeline
type: kubernetes
name: Container
steps:
- name: build & publish
image: spritsail/docker-build
context: .
settings:
repo: bv11-cr01.bessems.eu/library/pinniped-server
registry: bv11-cr01.bessems.eu
tags: latest
build_args:
- BUILDPLATFORM=linux/amd64
mtu: 1450
username:
from_secret: harbor_username
password:
from_secret: harbor_password

View File

@ -8,12 +8,6 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
- package-ecosystem: "gomod"
open-pull-requests-limit: 2
directory: "/hack/update-go-mod"
schedule:
interval: "daily"
- package-ecosystem: "docker" - package-ecosystem: "docker"
directory: "/" directory: "/"
schedule: schedule:

View File

@ -1,23 +1,18 @@
# See https://codeql.github.com and https://github.com/github/codeql-action
# This action runs GitHub's industry-leading semantic code analysis engine, CodeQL, against a
# repository's source code to find security vulnerabilities. It then automatically uploads the
# results to GitHub so they can be displayed in the repository's security tab.
name: "CodeQL" name: "CodeQL"
on: on:
push: push:
branches: [ "main", release* ] branches: [ main, release*, dynamic_clients ]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ "main" ] branches: [ main, release*, dynamic_clients ]
schedule: schedule:
- cron: '24 3 * * 3' - cron: '39 13 * * 2'
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} runs-on: ubuntu-latest
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions: permissions:
actions: read actions: read
contents: read contents: read
@ -30,35 +25,33 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file. # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
# If the Autobuild fails above, remove it and uncomment the following three lines. # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: | #- run: |
# echo "Run, Build Application using script" # make bootstrap
# ./location_of_script_within_repo/buildscript.sh # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v1
with:
category: "/language:${{matrix.language}}"

55
.github/workflows/scorecards.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Scorecards supply-chain security
on:
# Only the default branch is supported.
branch_protection_rule:
schedule:
- cron: '29 11 * * 3'
push:
branches: [ main, release* ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
actions: read
contents: read
steps:
- name: "Checkout code"
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # v1.0.4
with:
results_file: results.sarif
results_format: sarif
# Read-only PAT token. To create it,
# follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
# Publish the results to enable scorecard badges. For more details, see
# https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories, `publish_results` will automatically be set to `false`,
# regardless of the value entered here.
publish_results: true
# Upload the results as artifacts (optional).
- name: "Upload artifact"
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26
with:
sarif_file: results.sarif

3
.gitignore vendored
View File

@ -19,6 +19,3 @@
# MacOS Desktop Services Store # MacOS Desktop Services Store
.DS_Store .DS_Store
# Hugo temp file
.hugo_build.lock

View File

@ -114,6 +114,7 @@ go build -o pinniped ./cmd/pinniped
1. Install dependencies: 1. Install dependencies:
- [`chromedriver`](https://chromedriver.chromium.org/) (and [Chrome](https://www.google.com/chrome/))
- [`docker`](https://www.docker.com/) - [`docker`](https://www.docker.com/)
- `htpasswd` (installed by default on MacOS, usually found in `apache2-utils` package for linux) - `htpasswd` (installed by default on MacOS, usually found in `apache2-utils` package for linux)
- [`kapp`](https://carvel.dev/#getting-started) - [`kapp`](https://carvel.dev/#getting-started)
@ -121,13 +122,11 @@ go build -o pinniped ./cmd/pinniped
- [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)
- [`ytt`](https://carvel.dev/#getting-started) - [`ytt`](https://carvel.dev/#getting-started)
- [`nmap`](https://nmap.org/download.html) - [`nmap`](https://nmap.org/download.html)
- [`openssl`](https://www.openssl.org) (installed by default on MacOS)
- [Chrome](https://www.google.com/chrome/)
On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already): On macOS, these tools can be installed with [Homebrew](https://brew.sh/) (assuming you have Chrome installed already):
```bash ```bash
brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl nmap && brew cask install docker brew install kind vmware-tanzu/carvel/ytt vmware-tanzu/carvel/kapp kubectl chromedriver nmap && brew cask install docker
``` ```
1. Create a kind cluster, compile, create container images, and install Pinniped and supporting test dependencies using: 1. Create a kind cluster, compile, create container images, and install Pinniped and supporting test dependencies using:

View File

@ -1,31 +1,22 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. # Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Prepare to cross-compile by always running the build stage in the build platform, not the target platform. FROM golang:1.19.4 as build-env
FROM --platform=linux/amd64 golang:1.21.3 as build-env
WORKDIR /work WORKDIR /work
COPY . .
ARG GOPROXY ARG GOPROXY
ARG KUBE_GIT_VERSION # Build the executable binary (CGO_ENABLED=0 means static linking)
ENV KUBE_GIT_VERSION=$KUBE_GIT_VERSION # Pass in GOCACHE (build cache) and GOMODCACHE (module cache) so they
# can be re-used between image builds.
# These will be set by buildkit automatically, e.g. TARGETOS set to "linux" and TARGETARCH set to "amd64" or "arm64".
# Useful for building multi-arch container images.
ARG TARGETOS
ARG TARGETARCH
# Build the statically linked (CGO_ENABLED=0) binary.
# Mount source, build cache, and module cache for performance reasons.
# See https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
RUN \ RUN \
--mount=target=. \
--mount=type=cache,target=/cache/gocache \ --mount=type=cache,target=/cache/gocache \
--mount=type=cache,target=/cache/gomodcache \ --mount=type=cache,target=/cache/gomodcache \
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH && \ mkdir out && \
export GOCACHE=/cache/gocache GOMODCACHE=/cache/gomodcache CGO_ENABLED=0 GOOS=linux GOARCH=amd64 && \
go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-concierge-kube-cert-agent ./cmd/pinniped-concierge-kube-cert-agent/... && \ go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-concierge-kube-cert-agent ./cmd/pinniped-concierge-kube-cert-agent/... && \
go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-server ./cmd/pinniped-server/... && \ go build -v -trimpath -ldflags "$(hack/get-ldflags.sh) -w -s" -o /usr/local/bin/pinniped-server ./cmd/pinniped-server/... && \
ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-concierge && \ ln -s /usr/local/bin/pinniped-server /usr/local/bin/pinniped-concierge && \
@ -33,9 +24,6 @@ RUN \
ln -s /usr/local/bin/pinniped-server /usr/local/bin/local-user-authenticator ln -s /usr/local/bin/pinniped-server /usr/local/bin/local-user-authenticator
# Use a distroless runtime image with CA certificates, timezone data, and not much else. # Use a distroless runtime image with CA certificates, timezone data, and not much else.
# Note that we are not using --platform here, so it will choose the base image for the target platform, not the build platform.
# By using "distroless/static" instead of "distroless/static-debianXX" we can float on the latest stable version of debian.
# See https://github.com/GoogleContainerTools/distroless#base-operating-system
FROM gcr.io/distroless/static:nonroot@sha256:2a9e2b4fa771d31fe3346a873be845bfc2159695b9f90ca08e950497006ccc2e FROM gcr.io/distroless/static:nonroot@sha256:2a9e2b4fa771d31fe3346a873be845bfc2159695b9f90ca08e950497006ccc2e
# Copy the server binary from the build-env stage. # Copy the server binary from the build-env stage.

View File

@ -16,3 +16,9 @@
| Matt Moyer | [mattmoyer](https://github.com/mattmoyer) | | Matt Moyer | [mattmoyer](https://github.com/mattmoyer) |
| Mo Khan | [enj](https://github.com/enj) | | Mo Khan | [enj](https://github.com/enj) |
| Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) | | Pablo Schuhmacher | [pabloschuhmacher](https://github.com/pabloschuhmacher) |
## Pinniped Community Management
| Community Manager | GitHub ID |
|-------------------|---------------------------------------|
| Nigel Brown | [pnbrown](https://github.com/pnbrown) |

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -12,7 +12,7 @@ type JWTAuthenticatorStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
// Spec for configuring a JWT authenticator. // Spec for configuring a JWT authenticator.

View File

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

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -12,7 +12,7 @@ type WebhookAuthenticatorStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
// Spec for configuring a webhook authenticator. // Spec for configuring a webhook authenticator.

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -80,28 +80,6 @@ const (
ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None")
) )
// ImpersonationProxyTLSSpec contains information about how the Concierge impersonation proxy should
// serve TLS.
//
// If CertificateAuthorityData is not provided, the Concierge impersonation proxy will check the secret
// for a field called "ca.crt", which will be used as the CertificateAuthorityData.
//
// If neither CertificateAuthorityData nor ca.crt is provided, no CA bundle will be advertised for
// the impersonation proxy endpoint.
type ImpersonationProxyTLSSpec struct {
// X.509 Certificate Authority (base64-encoded PEM bundle).
// Used to advertise the CA bundle for the impersonation proxy endpoint.
//
// +optional
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
// SecretName is the name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
// the TLS serving certificate for the Concierge impersonation proxy endpoint.
//
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName,omitempty"`
}
// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. // ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
type ImpersonationProxySpec struct { type ImpersonationProxySpec struct {
// Mode configures whether the impersonation proxy should be started: // Mode configures whether the impersonation proxy should be started:
@ -122,13 +100,6 @@ type ImpersonationProxySpec struct {
// //
// +optional // +optional
ExternalEndpoint string `json:"externalEndpoint,omitempty"` ExternalEndpoint string `json:"externalEndpoint,omitempty"`
// TLS contains information about how the Concierge impersonation proxy should serve TLS.
//
// If this field is empty, the impersonation proxy will generate its own TLS certificate.
//
// +optional
TLS *ImpersonationProxyTLSSpec `json:"tls,omitempty"`
} }
// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. // ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -8,17 +8,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
type FederationDomainPhase string // +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
const ( const (
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources. SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
FederationDomainPhasePending FederationDomainPhase = "Pending" DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state. InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
) )
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider. // FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -45,157 +42,6 @@ type FederationDomainTLSSpec struct {
SecretName string `json:"secretName,omitempty"` SecretName string `json:"secretName,omitempty"`
} }
// FederationDomainTransformsConstant defines a constant variable and its value which will be made available to
// the transform expressions. This is a union type, and Type is the discriminator field.
type FederationDomainTransformsConstant struct {
// Name determines the name of the constant. It must be a valid identifier name.
// +kubebuilder:validation:Pattern=`^[a-zA-Z][_a-zA-Z0-9]*$`
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=64
Name string `json:"name"`
// Type determines the type of the constant, and indicates which other field should be non-empty.
// +kubebuilder:validation:Enum=string;stringList
Type string `json:"type"`
// StringValue should hold the value when Type is "string", and is otherwise ignored.
// +optional
StringValue string `json:"stringValue,omitempty"`
// StringListValue should hold the value when Type is "stringList", and is otherwise ignored.
// +optional
StringListValue []string `json:"stringListValue,omitempty"`
}
// FederationDomainTransformsExpression defines a transform expression.
type FederationDomainTransformsExpression struct {
// Type determines the type of the expression. It must be one of the supported types.
// +kubebuilder:validation:Enum=policy/v1;username/v1;groups/v1
Type string `json:"type"`
// Expression is a CEL expression that will be evaluated based on the Type during an authentication.
// +kubebuilder:validation:MinLength=1
Expression string `json:"expression"`
// Message is only used when Type is policy/v1. It defines an error message to be used when the policy rejects
// an authentication attempt. When empty, a default message will be used.
// +optional
Message string `json:"message,omitempty"`
}
// FederationDomainTransformsExample defines a transform example.
type FederationDomainTransformsExample struct {
// Username is the input username.
// +kubebuilder:validation:MinLength=1
Username string `json:"username"`
// Groups is the input list of group names.
// +optional
Groups []string `json:"groups,omitempty"`
// Expects is the expected output of the entire sequence of transforms when they are run against the
// input Username and Groups.
Expects FederationDomainTransformsExampleExpects `json:"expects"`
}
// FederationDomainTransformsExampleExpects defines the expected result for a transforms example.
type FederationDomainTransformsExampleExpects struct {
// Username is the expected username after the transformations have been applied.
// +optional
Username string `json:"username,omitempty"`
// Groups is the expected list of group names after the transformations have been applied.
// +optional
Groups []string `json:"groups,omitempty"`
// Rejected is a boolean that indicates whether authentication is expected to be rejected by a policy expression
// after the transformations have been applied. True means that it is expected that the authentication would be
// rejected. The default value of false means that it is expected that the authentication would not be rejected
// by any policy expression.
// +optional
Rejected bool `json:"rejected,omitempty"`
// Message is the expected error message of the transforms. When Rejected is true, then Message is the expected
// message for the policy which rejected the authentication attempt. When Rejected is true and Message is blank,
// then Message will be treated as the default error message for authentication attempts which are rejected by a
// policy. When Rejected is false, then Message is the expected error message for some other non-policy
// transformation error, such as a runtime error. When Rejected is false, there is no default expected Message.
// +optional
Message string `json:"message,omitempty"`
}
// FederationDomainTransforms defines identity transformations for an identity provider's usage on a FederationDomain.
type FederationDomainTransforms struct {
// Constants defines constant variables and their values which will be made available to the transform expressions.
// +patchMergeKey=name
// +patchStrategy=merge
// +listType=map
// +listMapKey=name
// +optional
Constants []FederationDomainTransformsConstant `json:"constants,omitempty"`
// Expressions are an optional list of transforms and policies to be executed in the order given during every
// authentication attempt, including during every session refresh.
// Each is a CEL expression. It may use the basic CEL language as defined in
// https://github.com/google/cel-spec/blob/master/doc/langdef.md plus the CEL string extensions defined in
// https://github.com/google/cel-go/tree/master/ext#strings.
//
// The username and groups extracted from the identity provider, and the constants defined in this CR, are
// available as variables in all expressions. The username is provided via a variable called `username` and
// the list of group names is provided via a variable called `groups` (which may be an empty list).
// Each user-provided constants is provided via a variable named `strConst.varName` for string constants
// and `strListConst.varName` for string list constants.
//
// The only allowed types for expressions are currently policy/v1, username/v1, and groups/v1.
// Each policy/v1 must return a boolean, and when it returns false, no more expressions from the list are evaluated
// and the authentication attempt is rejected.
// Transformations of type policy/v1 do not return usernames or group names, and therefore cannot change the
// username or group names.
// Each username/v1 transform must return the new username (a string), which can be the same as the old username.
// Transformations of type username/v1 do not return group names, and therefore cannot change the group names.
// Each groups/v1 transform must return the new groups list (list of strings), which can be the same as the old
// groups list.
// Transformations of type groups/v1 do not return usernames, and therefore cannot change the usernames.
// After each expression, the new (potentially changed) username or groups get passed to the following expression.
//
// Any compilation or static type-checking failure of any expression will cause an error status on the FederationDomain.
// During an authentication attempt, any unexpected runtime evaluation errors (e.g. division by zero) cause the
// authentication attempt to fail. When all expressions evaluate successfully, then the (potentially changed) username
// and group names have been decided for that authentication attempt.
//
// +optional
Expressions []FederationDomainTransformsExpression `json:"expressions,omitempty"`
// Examples can optionally be used to ensure that the sequence of transformation expressions are working as
// expected. Examples define sample input identities which are then run through the expression list, and the
// results are compared to the expected results. If any example in this list fails, then this
// identity provider will not be available for use within this FederationDomain, and the error(s) will be
// added to the FederationDomain status. This can be used to help guard against programming mistakes in the
// expressions, and also act as living documentation for other administrators to better understand the expressions.
// +optional
Examples []FederationDomainTransformsExample `json:"examples,omitempty"`
}
// FederationDomainIdentityProvider describes how an identity provider is made available in this FederationDomain.
type FederationDomainIdentityProvider struct {
// DisplayName is the name of this identity provider as it will appear to clients. This name ends up in the
// kubeconfig of end users, so changing the name of an identity provider that is in use by end users will be a
// disruptive change for those users.
// +kubebuilder:validation:MinLength=1
DisplayName string `json:"displayName"`
// ObjectRef is a reference to a Pinniped identity provider resource. A valid reference is required.
// If the reference cannot be resolved then the identity provider will not be made available.
// Must refer to a resource of one of the Pinniped identity provider types, e.g. OIDCIdentityProvider,
// LDAPIdentityProvider, ActiveDirectoryIdentityProvider.
ObjectRef corev1.TypedLocalObjectReference `json:"objectRef"`
// Transforms is an optional way to specify transformations to be applied during user authentication and
// session refresh.
// +optional
Transforms FederationDomainTransforms `json:"transforms,omitempty"`
}
// FederationDomainSpec is a struct that describes an OIDC Provider. // FederationDomainSpec is a struct that describes an OIDC Provider.
type FederationDomainSpec struct { type FederationDomainSpec struct {
// Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the // Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the
@ -209,35 +55,9 @@ type FederationDomainSpec struct {
// +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MinLength=1
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
// TLS specifies a secret which will contain Transport Layer Security (TLS) configuration for the FederationDomain. // TLS configures how this FederationDomain is served over Transport Layer Security (TLS).
// +optional // +optional
TLS *FederationDomainTLSSpec `json:"tls,omitempty"` TLS *FederationDomainTLSSpec `json:"tls,omitempty"`
// IdentityProviders is the list of identity providers available for use by this FederationDomain.
//
// An identity provider CR (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes how to connect to a server,
// how to talk in a specific protocol for authentication, and how to use the schema of that server/protocol to
// extract a normalized user identity. Normalized user identities include a username and a list of group names.
// In contrast, IdentityProviders describes how to use that normalized identity in those Kubernetes clusters which
// belong to this FederationDomain. Each entry in IdentityProviders can be configured with arbitrary transformations
// on that normalized identity. For example, a transformation can add a prefix to all usernames to help avoid
// accidental conflicts when multiple identity providers have different users with the same username (e.g.
// "idp1:ryan" versus "idp2:ryan"). Each entry in IdentityProviders can also implement arbitrary authentication
// rejection policies. Even though a user was able to authenticate with the identity provider, a policy can disallow
// the authentication to the Kubernetes clusters that belong to this FederationDomain. For example, a policy could
// disallow the authentication unless the user belongs to a specific group in the identity provider.
//
// For backwards compatibility with versions of Pinniped which predate support for multiple identity providers,
// an empty IdentityProviders list will cause the FederationDomain to use all available identity providers which
// exist in the same namespace, but also to reject all authentication requests when there is more than one identity
// provider currently defined. In this backwards compatibility mode, the name of the identity provider resource
// (e.g. the Name of an OIDCIdentityProvider resource) will be used as the name of the identity provider in this
// FederationDomain. This mode is provided to make upgrading from older versions easier. However, instead of
// relying on this backwards compatibility mode, please consider this mode to be deprecated and please instead
// explicitly list the identity provider using this IdentityProviders field.
//
// +optional
IdentityProviders []FederationDomainIdentityProvider `json:"identityProviders,omitempty"`
} }
// FederationDomainSecrets holds information about this OIDC Provider's secrets. // FederationDomainSecrets holds information about this OIDC Provider's secrets.
@ -266,17 +86,20 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Phase summarizes the overall status of the FederationDomain. // Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// +kubebuilder:default=Pending // represent success or failure.
// +kubebuilder:validation:Enum=Pending;Ready;Error // +optional
Phase FederationDomainPhase `json:"phase,omitempty"` Status FederationDomainStatusCondition `json:"status,omitempty"`
// Conditions represent the observations of an FederationDomain's current state. // Message provides human-readable details about the Status.
// +patchMergeKey=type // +optional
// +patchStrategy=merge Message string `json:"message,omitempty"`
// +listType=map
// +listMapKey=type // LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` // 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. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +111,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase` // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

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

View File

@ -1,4 +1,4 @@
// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources. // PhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending" PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state. // PhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready" PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state. // PhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error" PhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`
@ -85,7 +85,7 @@ type OIDCClientStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// totalClientSecrets is the current number of client secrets that are detected for this OIDCClient. // totalClientSecrets is the current number of client secrets that are detected for this OIDCClient.
// +optional // +optional

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -32,7 +32,7 @@ type ActiveDirectoryIdentityProviderStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
type ActiveDirectoryIdentityProviderBind struct { type ActiveDirectoryIdentityProviderBind struct {
@ -114,10 +114,9 @@ type ActiveDirectoryIdentityProviderGroupSearch struct {
// Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user. // Filter is the ActiveDirectory search filter which should be applied when searching for groups for a user.
// The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the // The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the
// value of an attribute of the user entry found as a result of the user search. Which attribute's // dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or
// value is used to replace the placeholder(s) depends on the value of UserAttributeForFilter. // "&(objectClass=groupOfNames)(member={})". For more information about ActiveDirectory filters, see
// E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". // https://ldap.com/ldap-filters.
// For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. // Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the filter were specified as // Optional. When not specified, the default will act as if the filter were specified as
// "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". // "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
@ -128,17 +127,6 @@ type ActiveDirectoryIdentityProviderGroupSearch struct {
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`
// UserAttributeForFilter specifies which attribute's value from the user entry found as a result of
// the user search will be used to replace the "{}" placeholder(s) in the group search Filter.
// For example, specifying "uid" as the UserAttributeForFilter while specifying
// "&(objectClass=posixGroup)(memberUid={})" as the Filter would search for groups by replacing
// the "{}" placeholder in the Filter with the value of the user's "uid" attribute.
// Optional. When not specified, the default will act as if "dn" were specified. For example, leaving
// UserAttributeForFilter unspecified while specifying "&(objectClass=groupOfNames)(member={})" as the Filter
// would search for groups by replacing the "{}" placeholder(s) with the dn (distinguished name) of the user.
// +optional
UserAttributeForFilter string `json:"userAttributeForFilter,omitempty"`
// Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as // Attributes specifies how the group's information should be read from each ActiveDirectory entry which was found as
// the result of the group search. // the result of the group search.
// +optional // +optional

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -32,7 +32,7 @@ type LDAPIdentityProviderStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
type LDAPIdentityProviderBind struct { type LDAPIdentityProviderBind struct {
@ -101,31 +101,20 @@ type LDAPIdentityProviderGroupSearch struct {
// Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g. // Base is the dn (distinguished name) that should be used as the search base when searching for groups. E.g.
// "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and // "ou=groups,dc=example,dc=com". When not specified, no group search will be performed and
// authenticated users will not belong to any groups from the LDAP provider. Also, when not specified, // authenticated users will not belong to any groups from the LDAP provider. Also, when not specified,
// the values of Filter, UserAttributeForFilter, Attributes, and SkipGroupRefresh are ignored. // the values of Filter and Attributes are ignored.
// +optional // +optional
Base string `json:"base,omitempty"` Base string `json:"base,omitempty"`
// Filter is the LDAP search filter which should be applied when searching for groups for a user. // Filter is the LDAP search filter which should be applied when searching for groups for a user.
// The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the // The pattern "{}" must occur in the filter at least once and will be dynamically replaced by the
// value of an attribute of the user entry found as a result of the user search. Which attribute's // dn (distinguished name) of the user entry found as a result of the user search. E.g. "member={}" or
// value is used to replace the placeholder(s) depends on the value of UserAttributeForFilter. // "&(objectClass=groupOfNames)(member={})". For more information about LDAP filters, see
// For more information about LDAP filters, see https://ldap.com/ldap-filters. // https://ldap.com/ldap-filters.
// Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used. // Note that the dn (distinguished name) is not an attribute of an entry, so "dn={}" cannot be used.
// Optional. When not specified, the default will act as if the Filter were specified as "member={}". // Optional. When not specified, the default will act as if the Filter were specified as "member={}".
// +optional // +optional
Filter string `json:"filter,omitempty"` Filter string `json:"filter,omitempty"`
// UserAttributeForFilter specifies which attribute's value from the user entry found as a result of
// the user search will be used to replace the "{}" placeholder(s) in the group search Filter.
// For example, specifying "uid" as the UserAttributeForFilter while specifying
// "&(objectClass=posixGroup)(memberUid={})" as the Filter would search for groups by replacing
// the "{}" placeholder in the Filter with the value of the user's "uid" attribute.
// Optional. When not specified, the default will act as if "dn" were specified. For example, leaving
// UserAttributeForFilter unspecified while specifying "&(objectClass=groupOfNames)(member={})" as the Filter
// would search for groups by replacing the "{}" placeholder(s) with the dn (distinguished name) of the user.
// +optional
UserAttributeForFilter string `json:"userAttributeForFilter,omitempty"`
// Attributes specifies how the group's information should be read from each LDAP entry which was found as // Attributes specifies how the group's information should be read from each LDAP entry which was found as
// the result of the group search. // the result of the group search.
// +optional // +optional

View File

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

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -32,7 +32,7 @@ type OIDCIdentityProviderStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
// OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization // OIDCAuthorizationConfig provides information about how to form the OAuth2 authorization
@ -143,12 +143,12 @@ type OIDCClaims struct {
// "additionalClaims" claim of the ID tokens generated by the Supervisor. This should be specified as a map of // "additionalClaims" claim of the ID tokens generated by the Supervisor. This should be specified as a map of
// new claim names as the keys, and upstream claim names as the values. These new claim names will be nested // new claim names as the keys, and upstream claim names as the values. These new claim names will be nested
// under the top-level "additionalClaims" claim in ID tokens generated by the Supervisor when this // under the top-level "additionalClaims" claim in ID tokens generated by the Supervisor when this
// OIDCIdentityProvider was used for user authentication. These claims will be made available to all clients. // OIDCIdentityProvider was used for user authentication. This feature is not required for using the Supervisor to
// This feature is not required to use the Supervisor to provide authentication for Kubernetes clusters, but can be // provide authentication for Kubernetes clusters, but can be used when using the Supervisor for other
// used when using the Supervisor for other authentication purposes. When this map is empty or the upstream claims // authentication purposes. When this map is empty, the "additionalClaims" claim will be excluded from the ID
// are not available, the "additionalClaims" claim will be excluded from the ID tokens generated by the Supervisor. // tokens generated by the Supervisor.
// +optional // +optional
AdditionalClaimMappings map[string]string `json:"additionalClaimMappings,omitempty"` AdditionalClaimMappings map[string]string `json:"additionalClaimMappings"`
} }
// OIDCClient contains information about an OIDC client (e.g., client ID and client // OIDCClient contains information about an OIDC client (e.g., client ID and client

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package oidc package oidc
@ -40,10 +40,6 @@ const (
// group names which were mapped from the upstream identity provider. // group names which were mapped from the upstream identity provider.
IDTokenClaimGroups = "groups" IDTokenClaimGroups = "groups"
// IDTokenClaimAdditionalClaims is the top level claim used to hold additional claims in the downstream ID
// token, if any claims are present.
IDTokenClaimAdditionalClaims = "additionalClaims"
// GrantTypeAuthorizationCode is the name of the grant type for authorization code flows defined by the OIDC spec. // GrantTypeAuthorizationCode is the name of the grant type for authorization code flows defined by the OIDC spec.
GrantTypeAuthorizationCode = "authorization_code" GrantTypeAuthorizationCode = "authorization_code"

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Package main is the combined entrypoint for the Pinniped "kube-cert-agent" component. // Package main is the combined entrypoint for the Pinniped "kube-cert-agent" component.
@ -13,23 +13,8 @@ import (
"os" "os"
"time" "time"
// This side effect import ensures that we use fipsonly crypto during TLS in fips_strict mode. // this side effect import ensures that we use fipsonly crypto in fips_strict mode.
// _ "go.pinniped.dev/internal/crypto/ptls"
// Commenting this out because it causes the runtime memory consumption of this binary to increase
// from ~1 MB to ~8 MB (as measured when running the sleep subcommand). This binary does not use TLS,
// so it should not be needed. If this binary is ever changed to make use of TLS client and/or server
// code, then we should bring this import back to support the use of the ptls library for client and
// server code, and we should also increase the memory limits on the kube cert agent deployment (as
// decided by the kube cert agent controller in the Concierge).
//
//nolint:godot // This is not sentence, it is a commented out line of import code.
// _ "go.pinniped.dev/internal/crypto/ptls"
// This side effect imports cgo so that runtime/cgo gets linked, when in fips_strict mode.
// Without this line, the binary will exit 133 upon startup in fips_strict mode.
// It also enables fipsonly tls mode, just to be absolutely sure that the fips code is enabled,
// even though it shouldn't be used currently by this binary.
_ "go.pinniped.dev/internal/crypto/fips"
) )
//nolint:gochecknoglobals // these are swapped during unit tests. //nolint:gochecknoglobals // these are swapped during unit tests.

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Package main is the combined entrypoint for all Pinniped server components. // Package main is the combined entrypoint for all Pinniped server components.
@ -14,8 +14,8 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
concierge "go.pinniped.dev/internal/concierge/server"
// this side effect import ensures that we use fipsonly crypto in fips_strict mode. // this side effect import ensures that we use fipsonly crypto in fips_strict mode.
concierge "go.pinniped.dev/internal/concierge/server"
_ "go.pinniped.dev/internal/crypto/ptls" _ "go.pinniped.dev/internal/crypto/ptls"
lua "go.pinniped.dev/internal/localuserauthenticator" lua "go.pinniped.dev/internal/localuserauthenticator"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"

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

@ -0,0 +1,22 @@
// Copyright 2020-2022 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

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -15,6 +15,7 @@ import (
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
"go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/testutil"
) )
func TestConciergeModeFlag(t *testing.T) { func TestConciergeModeFlag(t *testing.T) {
@ -51,7 +52,7 @@ func TestConciergeModeFlag(t *testing.T) {
func TestCABundleFlag(t *testing.T) { func TestCABundleFlag(t *testing.T) {
testCA, err := certauthority.New("Test CA", 1*time.Hour) testCA, err := certauthority.New("Test CA", 1*time.Hour)
require.NoError(t, err) require.NoError(t, err)
tmpdir := t.TempDir() tmpdir := testutil.TempDir(t)
emptyFilePath := filepath.Join(tmpdir, "empty") emptyFilePath := filepath.Join(tmpdir, "empty")
require.NoError(t, os.WriteFile(emptyFilePath, []byte{}, 0600)) require.NoError(t, os.WriteFile(emptyFilePath, []byte{}, 0600))

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -24,7 +24,7 @@ func generateMarkdownHelpCommand() *cobra.Command {
Args: cobra.NoArgs, Args: cobra.NoArgs,
Use: "generate-markdown-help", Use: "generate-markdown-help",
Short: "Generate markdown help for the current set of non-hidden CLI commands", Short: "Generate markdown help for the current set of non-hidden CLI commands",
SilenceUsage: true, // do not print usage message when commands fail SilenceUsage: true,
Hidden: true, Hidden: true,
RunE: runGenerateMarkdownHelp, RunE: runGenerateMarkdownHelp,
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -8,11 +8,7 @@ import (
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{Use: "get", Short: "get"}
Use: "get",
Short: "Gets one of [kubeconfig]",
SilenceUsage: true, // Do not print usage message when commands fail.
}
//nolint:gochecknoinits //nolint:gochecknoinits
func init() { func init() {

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -23,7 +23,6 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go _ "k8s.io/client-go/plugin/pkg/client/auth" // Adds handlers for various dynamic auth plugins in client-go
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api" clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/utils/strings/slices"
conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1" conciergev1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1" configv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
@ -96,12 +95,6 @@ type getKubeconfigParams struct {
credentialCachePath string credentialCachePath string
credentialCachePathSet bool credentialCachePathSet bool
installHint string installHint string
pinnipedCliPath string
}
type discoveryResponseScopesSupported struct {
// Same as ScopesSupported in the Supervisor's discovery handler's struct.
ScopesSupported []string `json:"scopes_supported"`
} }
func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command { func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
@ -110,7 +103,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
Args: cobra.NoArgs, Args: cobra.NoArgs,
Use: "kubeconfig", Use: "kubeconfig",
Short: "Generate a Pinniped-based kubeconfig for a cluster", Short: "Generate a Pinniped-based kubeconfig for a cluster",
SilenceUsage: true, // do not print usage message when commands fail SilenceUsage: true,
} }
flags getKubeconfigParams flags getKubeconfigParams
namespace string // unused now namespace string // unused now
@ -152,16 +145,14 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)") f.StringVarP(&flags.outputPath, "output", "o", "", "Output file path (default: stdout)")
f.StringVar(&flags.generatedNameSuffix, "generated-name-suffix", "-pinniped", "Suffix to append to generated cluster, context, user kubeconfig entries") f.StringVar(&flags.generatedNameSuffix, "generated-name-suffix", "-pinniped", "Suffix to append to generated cluster, context, user kubeconfig entries")
f.StringVar(&flags.credentialCachePath, "credential-cache", "", "Path to cluster-specific credentials cache") f.StringVar(&flags.credentialCachePath, "credential-cache", "", "Path to cluster-specific credentials cache")
f.StringVar(&flags.pinnipedCliPath, "pinniped-cli-path", "", "Full path or executable name for the Pinniped CLI binary to be embedded in the resulting kubeconfig output (e.g. 'pinniped') (default: full path of the binary used to execute this command)")
f.StringVar(&flags.installHint, "install-hint", "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details", "This text is shown to the user when the pinniped CLI is not installed.") f.StringVar(&flags.installHint, "install-hint", "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details", "This text is shown to the user when the pinniped CLI is not installed.")
mustMarkHidden(cmd, "oidc-debug-session-cache")
mustMarkHidden(cmd, // --oidc-skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
"oidc-debug-session-cache", mustMarkHidden(cmd, "oidc-skip-listen")
"oidc-skip-listen", // --oidc-skip-listen is mainly needed for testing. We'll leave it hidden until we have a non-testing use case.
"concierge-namespace",
)
mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore") mustMarkDeprecated(cmd, "concierge-namespace", "not needed anymore")
mustMarkHidden(cmd, "concierge-namespace")
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
if flags.outputPath != "" { if flags.outputPath != "" {
@ -241,9 +232,11 @@ func runGetKubeconfig(ctx context.Context, out io.Writer, deps kubeconfigDeps, f
cluster.CertificateAuthorityData = flags.concierge.caBundle cluster.CertificateAuthorityData = flags.concierge.caBundle
} }
if len(flags.oidc.issuer) > 0 { // If there is an issuer, and if any upstream IDP flags are not already set, then try to discover Supervisor upstream IDP details.
err = pinnipedSupervisorDiscovery(ctx, &flags, deps.log) // When all the upstream IDP flags are set by the user, then skip discovery and don't validate their input. Maybe they know something
if err != nil { // that we can't know, like the name of an IDP that they are going to define in the future.
if len(flags.oidc.issuer) > 0 && (flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "" || flags.oidc.upstreamIDPFlow == "") {
if err := discoverSupervisorUpstreamIDP(ctx, &flags, deps.log); err != nil {
return err return err
} }
} }
@ -271,12 +264,7 @@ func newExecConfig(deps kubeconfigDeps, flags getKubeconfigParams) (*clientcmdap
execConfig.InstallHint = flags.installHint execConfig.InstallHint = flags.installHint
var err error var err error
execConfig.Command, err = func() (string, error) { execConfig.Command, err = deps.getPathToSelf()
if flags.pinnipedCliPath != "" {
return flags.pinnipedCliPath, nil
}
return deps.getPathToSelf()
}()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not determine the Pinniped executable path: %w", err) return nil, fmt.Errorf("could not determine the Pinniped executable path: %w", err)
} }
@ -732,7 +720,6 @@ func validateKubeconfig(ctx context.Context, flags getKubeconfigParams, kubeconf
func countCACerts(pemData []byte) int { func countCACerts(pemData []byte) int {
pool := x509.NewCertPool() pool := x509.NewCertPool()
pool.AppendCertsFromPEM(pemData) pool.AppendCertsFromPEM(pemData)
//nolint:staticcheck // since we're not using .Subjects() to access the system pool
return len(pool.Subjects()) return len(pool.Subjects())
} }
@ -745,75 +732,21 @@ func hasPendingStrategy(credentialIssuer *configv1alpha1.CredentialIssuer) bool
return false return false
} }
func pinnipedSupervisorDiscovery(ctx context.Context, flags *getKubeconfigParams, log plog.MinLogger) error { func discoverSupervisorUpstreamIDP(ctx context.Context, flags *getKubeconfigParams, log plog.MinLogger) error {
// Make a client suitable for calling the provider, which may or may not be a Pinniped Supervisor. httpClient, err := newDiscoveryHTTPClient(flags.oidc.caBundle)
oidcProviderHTTPClient, err := newDiscoveryHTTPClient(flags.oidc.caBundle)
if err != nil { if err != nil {
return err return err
} }
// Call the provider's discovery endpoint, but don't parse the results yet. pinnipedIDPsEndpoint, err := discoverIDPsDiscoveryEndpointURL(ctx, flags.oidc.issuer, httpClient)
discoveredProvider, err := discoverOIDCProvider(ctx, flags.oidc.issuer, oidcProviderHTTPClient)
if err != nil {
return err
}
// Parse the discovery response to find the Supervisor IDP discovery endpoint.
pinnipedIDPsEndpoint, err := discoverIDPsDiscoveryEndpointURL(discoveredProvider)
if err != nil { if err != nil {
return err return err
} }
if pinnipedIDPsEndpoint == "" { if pinnipedIDPsEndpoint == "" {
// The issuer is not advertising itself as a Pinniped Supervisor which supports upstream IDP discovery. // The issuer is not advertising itself as a Pinniped Supervisor which supports upstream IDP discovery.
// Since this field is not present, then assume that the provider is not a Pinniped Supervisor. This field
// was added to the discovery response in v0.9.0, which is so long ago that we can assume there are no such
// old Supervisors in the wild which need to work with this CLI command anymore. Since the issuer is not a
// Supervisor, then there is no need to do the rest of the Supervisor-specific business logic below related
// to username/groups scopes or IDP types/names/flows.
return nil return nil
} }
// Now that we know that the provider is a Supervisor, perform an additional check based on its response.
// The username and groups scopes were added to the Supervisor in v0.20.0, and were also added to the
// "scopes_supported" field in the discovery response in that same version. If this CLI command is talking
// to an older Supervisor, then remove the username and groups scopes from the list of requested scopes
// since they will certainly cause an error from the old Supervisor during authentication.
supervisorSupportsBothUsernameAndGroupsScopes, err := discoverScopesSupportedIncludesBothUsernameAndGroups(discoveredProvider)
if err != nil {
return err
}
if !supervisorSupportsBothUsernameAndGroupsScopes {
flags.oidc.scopes = slices.Filter(nil, flags.oidc.scopes, func(scope string) bool {
if scope == oidcapi.ScopeUsername || scope == oidcapi.ScopeGroups {
log.Info("removed scope from --oidc-scopes list because it is not supported by this Supervisor", "scope", scope)
return false // Remove username and groups scopes if there were present in the flags.
}
return true // Keep any other scopes in the flag list.
})
}
// If any upstream IDP flags are not already set, then try to discover Supervisor upstream IDP details.
// When all the upstream IDP flags are set by the user, then skip discovery and don't validate their input.
// Maybe they know something that we can't know, like the name of an IDP that they are going to define in the
// future.
if flags.oidc.upstreamIDPType == "" || flags.oidc.upstreamIDPName == "" || flags.oidc.upstreamIDPFlow == "" {
if err := discoverSupervisorUpstreamIDP(ctx, pinnipedIDPsEndpoint, oidcProviderHTTPClient, flags, log); err != nil {
return err
}
}
return nil
}
func discoverOIDCProvider(ctx context.Context, issuer string, httpClient *http.Client) (*coreosoidc.Provider, error) {
discoveredProvider, err := coreosoidc.NewProvider(coreosoidc.ClientContext(ctx, httpClient), issuer)
if err != nil {
return nil, fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
}
return discoveredProvider, nil
}
func discoverSupervisorUpstreamIDP(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client, flags *getKubeconfigParams, log plog.MinLogger) error {
discoveredUpstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient) discoveredUpstreamIDPs, err := discoverAllAvailableSupervisorUpstreamIDPs(ctx, pinnipedIDPsEndpoint, httpClient)
if err != nil { if err != nil {
return err return err
@ -853,22 +786,19 @@ func newDiscoveryHTTPClient(caBundleFlag caBundleFlag) (*http.Client, error) {
return phttp.Default(rootCAs), nil return phttp.Default(rootCAs), nil
} }
func discoverIDPsDiscoveryEndpointURL(discoveredProvider *coreosoidc.Provider) (string, error) { func discoverIDPsDiscoveryEndpointURL(ctx context.Context, issuer string, httpClient *http.Client) (string, error) {
var body idpdiscoveryv1alpha1.OIDCDiscoveryResponse discoveredProvider, err := coreosoidc.NewProvider(coreosoidc.ClientContext(ctx, httpClient), issuer)
err := discoveredProvider.Claims(&body)
if err != nil { if err != nil {
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err) return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
} }
return body.SupervisorDiscovery.PinnipedIDPsEndpoint, nil
var body idpdiscoveryv1alpha1.OIDCDiscoveryResponse
err = discoveredProvider.Claims(&body)
if err != nil {
return "", fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
} }
func discoverScopesSupportedIncludesBothUsernameAndGroups(discoveredProvider *coreosoidc.Provider) (bool, error) { return body.SupervisorDiscovery.PinnipedIDPsEndpoint, nil
var body discoveryResponseScopesSupported
err := discoveredProvider.Claims(&body)
if err != nil {
return false, fmt.Errorf("while fetching OIDC discovery data from issuer: %w", err)
}
return slices.Contains(body.ScopesSupported, oidcapi.ScopeUsername) && slices.Contains(body.ScopesSupported, oidcapi.ScopeGroups), nil
} }
func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]idpdiscoveryv1alpha1.PinnipedIDP, error) { func discoverAllAvailableSupervisorUpstreamIDPs(ctx context.Context, pinnipedIDPsEndpoint string, httpClient *http.Client) ([]idpdiscoveryv1alpha1.PinnipedIDP, error) {

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -32,7 +32,7 @@ import (
func TestGetKubeconfig(t *testing.T) { func TestGetKubeconfig(t *testing.T) {
testOIDCCA, err := certauthority.New("Test CA", 1*time.Hour) testOIDCCA, err := certauthority.New("Test CA", 1*time.Hour)
require.NoError(t, err) require.NoError(t, err)
tmpdir := t.TempDir() tmpdir := testutil.TempDir(t)
testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem") testOIDCCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, os.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600)) require.NoError(t, os.WriteFile(testOIDCCABundlePath, testOIDCCA.Bundle(), 0600))
@ -81,7 +81,6 @@ func TestGetKubeconfig(t *testing.T) {
"discovery.supervisor.pinniped.dev/v1alpha1": { "discovery.supervisor.pinniped.dev/v1alpha1": {
"pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers" "pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers"
}, },
"scopes_supported": ["openid", "offline_access", "pinniped:request-audience", "username", "groups"],
"another-key": "another-value" "another-key": "another-value"
}`, issuerURL, issuerURL) }`, issuerURL, issuerURL)
} }
@ -108,7 +107,7 @@ func TestGetKubeconfig(t *testing.T) {
wantLogs func(string, string) []string wantLogs func(string, string) []string
wantError bool wantError bool
wantStdout func(string, string) string wantStdout func(string, string) string
wantStderr func(string, string) testutil.RequireErrorStringFunc wantStderr func(string, string) string
wantOptionsCount int wantOptionsCount int
wantAPIGroupSuffix string wantAPIGroupSuffix string
}{ }{
@ -147,7 +146,6 @@ func TestGetKubeconfig(t *testing.T) {
--oidc-session-cache string Path to OpenID Connect session cache file --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) --oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
-o, --output string Output file path (default: stdout) -o, --output string Output file path (default: stdout)
--pinniped-cli-path string Full path or executable name for the Pinniped CLI binary to be embedded in the resulting kubeconfig output (e.g. 'pinniped') (default: full path of the binary used to execute this command)
--skip-validation Skip final validation of the kubeconfig (default: false) --skip-validation Skip final validation of the kubeconfig (default: false)
--static-token string Instead of doing an OIDC-based login, specify a static token --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 --static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
@ -166,8 +164,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: invalid argument "./does/not/exist" for "--oidc-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n") return `Error: invalid argument "./does/not/exist" for "--oidc-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n"
}, },
}, },
{ {
@ -179,8 +177,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: invalid argument "./does/not/exist" for "--concierge-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n") return `Error: invalid argument "./does/not/exist" for "--concierge-ca-bundle" flag: could not read CA bundle path: open ./does/not/exist: no such file or directory` + "\n"
}, },
}, },
{ {
@ -191,8 +189,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory` + "\n") return `Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory` + "\n"
}, },
}, },
{ {
@ -204,8 +202,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"` + "\n") return `Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"` + "\n"
}, },
}, },
{ {
@ -217,8 +215,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not load --kubeconfig/--kubeconfig-context: no such cluster "invalid-cluster"` + "\n") return `Error: could not load --kubeconfig/--kubeconfig-context: no such cluster "invalid-cluster"` + "\n"
}, },
}, },
{ {
@ -230,8 +228,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not load --kubeconfig/--kubeconfig-context: no such user "invalid-user"` + "\n") return `Error: could not load --kubeconfig/--kubeconfig-context: no such user "invalid-user"` + "\n"
}, },
}, },
{ {
@ -243,8 +241,8 @@ func TestGetKubeconfig(t *testing.T) {
}, },
getClientsetErr: fmt.Errorf("some kube error"), getClientsetErr: fmt.Errorf("some kube error"),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not configure Kubernetes client: some kube error` + "\n") return `Error: could not configure Kubernetes client: some kube error` + "\n"
}, },
}, },
{ {
@ -255,8 +253,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no CredentialIssuers were found` + "\n") return `Error: no CredentialIssuers were found` + "\n"
}, },
}, },
{ {
@ -273,8 +271,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: credentialissuers.config.concierge.pinniped.dev "does-not-exist" not found` + "\n") return `Error: credentialissuers.config.concierge.pinniped.dev "does-not-exist" not found` + "\n"
}, },
}, },
{ {
@ -297,8 +295,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n") return `Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n"
}, },
}, },
{ {
@ -321,8 +319,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n") return `Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found` + "\n"
}, },
}, },
{ {
@ -345,8 +343,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"` + "\n") return `Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"` + "\n"
}, },
}, },
{ {
@ -376,8 +374,8 @@ func TestGetKubeconfig(t *testing.T) {
}, },
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: failed to list JWTAuthenticator objects for autodiscovery: some list error` + "\n") return `Error: failed to list JWTAuthenticator objects for autodiscovery: some list error` + "\n"
}, },
}, },
{ {
@ -407,8 +405,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error` + "\n") return `Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error` + "\n"
}, },
}, },
{ {
@ -429,8 +427,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no authenticators were found` + "\n") return `Error: no authenticators were found` + "\n"
}, },
}, },
{ {
@ -459,8 +457,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified` + "\n") return `Error: multiple authenticators were found, so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified` + "\n"
}, },
}, },
{ {
@ -493,8 +491,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not autodiscover --concierge-mode` + "\n") return `Error: could not autodiscover --concierge-mode` + "\n"
}, },
}, },
{ {
@ -555,8 +553,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: autodiscovered Concierge CA bundle is invalid: illegal base64 data at input byte 7` + "\n") return `Error: autodiscovered Concierge CA bundle is invalid: illegal base64 data at input byte 7` + "\n"
}, },
}, },
{ {
@ -582,8 +580,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not autodiscover --oidc-issuer and none was provided` + "\n") return `Error: could not autodiscover --oidc-issuer and none was provided` + "\n"
}, },
}, },
{ {
@ -637,8 +635,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7` + "\n") return `Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7` + "\n"
}, },
}, },
{ {
@ -677,8 +675,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: request audience is not allowed to include the substring '.pinniped.dev': some-test-audience.pinniped.dev-invalid-substring` + "\n") return `Error: request audience is not allowed to include the substring '.pinniped.dev': some-test-audience.pinniped.dev-invalid-substring` + "\n"
}, },
}, },
{ {
@ -708,8 +706,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: request audience is not allowed to include the substring '.pinniped.dev': some-test-audience.pinniped.dev-invalid-substring` + "\n") return `Error: request audience is not allowed to include the substring '.pinniped.dev': some-test-audience.pinniped.dev-invalid-substring` + "\n"
}, },
}, },
{ {
@ -740,8 +738,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: could not determine the Pinniped executable path: some OS error` + "\n") return `Error: could not determine the Pinniped executable path: some OS error` + "\n"
}, },
}, },
{ {
@ -769,8 +767,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: only one of --static-token and --static-token-env can be specified` + "\n") return `Error: only one of --static-token and --static-token-env can be specified` + "\n"
}, },
}, },
{ {
@ -781,8 +779,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')` + "\n") return `Error: invalid API group suffix: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')` + "\n"
}, },
}, },
{ {
@ -813,8 +811,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString("Error: while fetching OIDC discovery data from issuer: 400 Bad Request: {}\n") return "Error: while fetching OIDC discovery data from issuer: 400 Bad Request: {}\n"
}, },
}, },
{ {
@ -849,8 +847,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantSprintfErrorString( return fmt.Sprintf(
"Error: while fetching OIDC discovery data from issuer: oidc: issuer did not match the issuer returned by provider, expected \"%s\" got \"https://wrong-issuer.com\"\n", "Error: while fetching OIDC discovery data from issuer: oidc: issuer did not match the issuer returned by provider, expected \"%s\" got \"https://wrong-issuer.com\"\n",
issuerURL) issuerURL)
}, },
@ -884,8 +882,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString("Error: unable to fetch IDP discovery data from issuer: unexpected http response status: 400 Bad Request\n") return "Error: unable to fetch IDP discovery data from issuer: unexpected http response status: 400 Bad Request\n"
}, },
}, },
{ {
@ -922,10 +920,10 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: multiple Supervisor upstream identity providers were found, ` + return `Error: multiple Supervisor upstream identity providers were found, ` +
`so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified. ` + `so the --upstream-identity-provider-name/--upstream-identity-provider-type flags must be specified. ` +
`Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"}]` + "\n") `Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -958,8 +956,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString("Error: while fetching OIDC discovery data from issuer: oidc: failed to decode provider discovery object: got Content-Type = application/json, but could not unmarshal as JSON: invalid character 'h' in literal true (expecting 'r')\n") return "Error: while fetching OIDC discovery data from issuer: oidc: failed to decode provider discovery object: got Content-Type = application/json, but could not unmarshal as JSON: invalid character 'h' in literal true (expecting 'r')\n"
}, },
}, },
{ {
@ -991,8 +989,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString("Error: unable to fetch IDP discovery data from issuer: could not parse response JSON: invalid character 'h' in literal true (expecting 'r')\n") return "Error: unable to fetch IDP discovery data from issuer: could not parse response JSON: invalid character 'h' in literal true (expecting 'r')\n"
}, },
}, },
{ {
@ -1027,8 +1025,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantX509UntrustedCertErrorString(fmt.Sprintf("Error: while fetching OIDC discovery data from issuer: Get \"%s/.well-known/openid-configuration\": %%s\n", issuerURL), "Acme Co") return fmt.Sprintf("Error: while fetching OIDC discovery data from issuer: Get \"%s/.well-known/openid-configuration\": %s\n", issuerURL, testutil.X509UntrustedCertError("Acme Co"))
}, },
}, },
{ {
@ -1065,8 +1063,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: while fetching OIDC discovery data from issuer: parse "https%://bad-issuer-url/.well-known/openid-configuration": first path segment in URL cannot contain colon` + "\n") return `Error: while fetching OIDC discovery data from issuer: parse "https%://bad-issuer-url/.well-known/openid-configuration": first path segment in URL cannot contain colon` + "\n"
}, },
}, },
{ {
@ -1088,8 +1086,7 @@ func TestGetKubeconfig(t *testing.T) {
"issuer": "%s", "issuer": "%s",
"discovery.supervisor.pinniped.dev/v1alpha1": { "discovery.supervisor.pinniped.dev/v1alpha1": {
"pinniped_identity_providers_endpoint": "https%%://illegal_url" "pinniped_identity_providers_endpoint": "https%%://illegal_url"
}, }
"scopes_supported": ["openid", "offline_access", "pinniped:request-audience", "username", "groups"]
}`, issuerURL) }`, issuerURL)
}, },
wantLogs: func(issuerCABundle string, issuerURL string) []string { wantLogs: func(issuerCABundle string, issuerURL string) []string {
@ -1105,8 +1102,8 @@ func TestGetKubeconfig(t *testing.T) {
} }
}, },
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: while forming request to IDP discovery URL: parse "https%://illegal_url": first path segment in URL cannot contain colon` + "\n") return `Error: while forming request to IDP discovery URL: parse "https%://illegal_url": first path segment in URL cannot contain colon` + "\n"
}, },
}, },
{ {
@ -1131,9 +1128,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no Supervisor upstream identity providers with name "does-not-exist-idp" of type "ldap" were found.` + return `Error: no Supervisor upstream identity providers with name "does-not-exist-idp" of type "ldap" were found.` +
` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"}]` + "\n") ` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"}]` + "\n"
}, },
}, },
{ {
@ -1159,10 +1156,10 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: multiple Supervisor upstream identity providers of type "ldap" were found,` + return `Error: multiple Supervisor upstream identity providers of type "ldap" were found,` +
` so the --upstream-identity-provider-name flag must be specified.` + ` so the --upstream-identity-provider-name flag must be specified.` +
` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"some-ldap-idp","type":"ldap"},{"name":"some-other-ldap-idp","type":"ldap"},{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1187,10 +1184,10 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: multiple Supervisor upstream identity providers with name "my-idp" were found,` + return `Error: multiple Supervisor upstream identity providers with name "my-idp" were found,` +
` so the --upstream-identity-provider-type flag must be specified.` + ` so the --upstream-identity-provider-type flag must be specified.` +
` Found these upstreams: [{"name":"my-idp","type":"ldap"},{"name":"my-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"my-idp","type":"ldap"},{"name":"my-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1214,9 +1211,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no Supervisor upstream identity providers of type "ldap" were found.` + return `Error: no Supervisor upstream identity providers of type "ldap" were found.` +
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1239,9 +1236,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no Supervisor upstream identity providers of type "ldap" were found.` + return `Error: no Supervisor upstream identity providers of type "ldap" were found.` +
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1265,9 +1262,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` + return `Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` +
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"},{"name":"some-other-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1290,9 +1287,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` + return `Error: no Supervisor upstream identity providers with name "my-nonexistent-idp" were found.` +
` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n") ` Found these upstreams: [{"name":"some-oidc-idp","type":"oidc"}]` + "\n"
}, },
}, },
{ {
@ -1315,9 +1312,9 @@ func TestGetKubeconfig(t *testing.T) {
] ]
}`), }`),
wantError: true, wantError: true,
wantStderr: func(issuerCABundle string, issuerURL string) testutil.RequireErrorStringFunc { wantStderr: func(issuerCABundle string, issuerURL string) string {
return testutil.WantExactErrorString(`Error: no client flow "my-nonexistent-flow" for Supervisor upstream identity provider "some-oidc-idp" of type "oidc" were found.` + return `Error: no client flow "my-nonexistent-flow" for Supervisor upstream identity provider "some-oidc-idp" of type "oidc" were found.` +
` Found these flows: [non-matching-flow-1 non-matching-flow-2]` + "\n") ` Found these flows: [non-matching-flow-1 non-matching-flow-2]` + "\n"
}, },
}, },
{ {
@ -1584,6 +1581,7 @@ func TestGetKubeconfig(t *testing.T) {
}, },
}, },
{ {
name: "autodetect nothing, set a bunch of options", name: "autodetect nothing, set a bunch of options",
args: func(issuerCABundle string, issuerURL string) []string { args: func(issuerCABundle string, issuerURL string) []string {
f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle) f := testutil.WriteStringToTempFile(t, "testca-*.pem", issuerCABundle)
@ -1607,7 +1605,6 @@ func TestGetKubeconfig(t *testing.T) {
"--skip-validation", "--skip-validation",
"--generated-name-suffix", "-sso", "--generated-name-suffix", "-sso",
"--credential-cache", "/path/to/cache/dir/credentials.yaml", "--credential-cache", "/path/to/cache/dir/credentials.yaml",
"--pinniped-cli-path", "/some/path/to/command-exe",
} }
}, },
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object { conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
@ -1659,7 +1656,7 @@ func TestGetKubeconfig(t *testing.T) {
- --session-cache=/path/to/cache/dir/sessions.yaml - --session-cache=/path/to/cache/dir/sessions.yaml
- --debug-session-cache - --debug-session-cache
- --request-audience=test-audience - --request-audience=test-audience
command: /some/path/to/command-exe command: '.../path/to/pinniped'
env: [] env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details for more details
@ -2277,271 +2274,6 @@ func TestGetKubeconfig(t *testing.T) {
base64.StdEncoding.EncodeToString([]byte(issuerCABundle))) base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
}, },
}, },
{
name: "IDP discovery endpoint is listed in OIDC discovery document but scopes_supported does not include username or groups, so do not request username or groups in kubeconfig's --scopes",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticator(issuerCABundle, issuerURL),
}
},
oidcDiscoveryResponse: func(issuerURL string) string {
return here.Docf(`{
"issuer": "%s",
"discovery.supervisor.pinniped.dev/v1alpha1": {
"pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers"
},
"scopes_supported": ["openid", "offline_access", "pinniped:request-audience"]
}`, issuerURL, issuerURL)
},
idpsDiscoveryResponse: here.Docf(`{
"pinniped_identity_providers": [
{"name": "some-oidc-idp", "type": "oidc"}
]
}`),
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
`"level"=0 "msg"="removed scope from --oidc-scopes list because it is not supported by this Supervisor" "scope"="username"`,
`"level"=0 "msg"="removed scope from --oidc-scopes list because it is not supported by this Supervisor" "scope"="groups"`,
}
},
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience
- --ca-bundle-data=%s
- --request-audience=test-audience
- --upstream-identity-provider-name=some-oidc-idp
- --upstream-identity-provider-type=oidc
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "IDP discovery endpoint is listed in OIDC discovery document but scopes_supported is not listed (which shouldn't really happen), so do not request username or groups in kubeconfig's --scopes",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticator(issuerCABundle, issuerURL),
}
},
oidcDiscoveryResponse: func(issuerURL string) string {
return here.Docf(`{
"issuer": "%s",
"discovery.supervisor.pinniped.dev/v1alpha1": {
"pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers"
}
}`, issuerURL, issuerURL)
},
idpsDiscoveryResponse: here.Docf(`{
"pinniped_identity_providers": [
{"name": "some-oidc-idp", "type": "oidc"}
]
}`),
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
`"level"=0 "msg"="removed scope from --oidc-scopes list because it is not supported by this Supervisor" "scope"="username"`,
`"level"=0 "msg"="removed scope from --oidc-scopes list because it is not supported by this Supervisor" "scope"="groups"`,
}
},
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=offline_access,openid,pinniped:request-audience
- --ca-bundle-data=%s
- --request-audience=test-audience
- --upstream-identity-provider-name=some-oidc-idp
- --upstream-identity-provider-type=oidc
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{
name: "IDP discovery endpoint is listed in OIDC discovery document but scopes_supported does not include username or groups, and scopes username and groups were also not requested by flags",
args: func(issuerCABundle string, issuerURL string) []string {
return []string{
"--kubeconfig", "./testdata/kubeconfig.yaml",
"--skip-validation",
"--oidc-scopes", "foo,bar,baz",
}
},
conciergeObjects: func(issuerCABundle string, issuerURL string) []runtime.Object {
return []runtime.Object{
credentialIssuer(),
jwtAuthenticator(issuerCABundle, issuerURL),
}
},
oidcDiscoveryResponse: func(issuerURL string) string {
return here.Docf(`{
"issuer": "%s",
"discovery.supervisor.pinniped.dev/v1alpha1": {
"pinniped_identity_providers_endpoint": "%s/v1alpha1/pinniped_identity_providers"
},
"scopes_supported": ["openid", "offline_access", "pinniped:request-audience"]
}`, issuerURL, issuerURL)
},
idpsDiscoveryResponse: here.Docf(`{
"pinniped_identity_providers": [
{"name": "some-oidc-idp", "type": "oidc"}
]
}`),
wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
`"level"=0 "msg"="discovered Concierge operating in TokenCredentialRequest API mode"`,
`"level"=0 "msg"="discovered Concierge endpoint" "endpoint"="https://fake-server-url-value"`,
`"level"=0 "msg"="discovered Concierge certificate authority bundle" "roots"=0`,
`"level"=0 "msg"="discovered JWTAuthenticator" "name"="test-authenticator"`,
fmt.Sprintf(`"level"=0 "msg"="discovered OIDC issuer" "issuer"="%s"`, issuerURL),
`"level"=0 "msg"="discovered OIDC audience" "audience"="test-audience"`,
`"level"=0 "msg"="discovered OIDC CA bundle" "roots"=1`,
}
},
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Docf(`
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
server: https://fake-server-url-value
name: kind-cluster-pinniped
contexts:
- context:
cluster: kind-cluster-pinniped
user: kind-user-pinniped
name: kind-context-pinniped
current-context: kind-context-pinniped
kind: Config
preferences: {}
users:
- name: kind-user-pinniped
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
args:
- login
- oidc
- --enable-concierge
- --concierge-api-group-suffix=pinniped.dev
- --concierge-authenticator-name=test-authenticator
- --concierge-authenticator-type=jwt
- --concierge-endpoint=https://fake-server-url-value
- --concierge-ca-bundle-data=ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==
- --issuer=%s
- --client-id=pinniped-cli
- --scopes=foo,bar,baz
- --ca-bundle-data=%s
- --request-audience=test-audience
- --upstream-identity-provider-name=some-oidc-idp
- --upstream-identity-provider-type=oidc
command: '.../path/to/pinniped'
env: []
installHint: The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli
for more details
provideClusterInfo: true
`,
issuerURL,
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
},
},
{ {
name: "when all upstream IDP related flags are sent, pass them through without performing IDP discovery", name: "when all upstream IDP related flags are sent, pass them through without performing IDP discovery",
args: func(issuerCABundle string, issuerURL string) []string { args: func(issuerCABundle string, issuerURL string) []string {
@ -2559,8 +2291,7 @@ func TestGetKubeconfig(t *testing.T) {
jwtAuthenticator(issuerCABundle, issuerURL), jwtAuthenticator(issuerCABundle, issuerURL),
} }
}, },
oidcDiscoveryResponse: happyOIDCDiscoveryResponse, // still called to check for support of username and groups scopes oidcDiscoveryStatusCode: http.StatusNotFound, // should not get called by the client in this case
idpsDiscoveryStatusCode: http.StatusNotFound, // should not get called by the client in this case
wantLogs: func(issuerCABundle string, issuerURL string) []string { wantLogs: func(issuerCABundle string, issuerURL string) []string {
return []string{ return []string{
`"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`, `"level"=0 "msg"="discovered CredentialIssuer" "name"="test-credential-issuer"`,
@ -3284,12 +3015,11 @@ func TestGetKubeconfig(t *testing.T) {
} }
require.Equal(t, expectedStdout, stdout.String(), "unexpected stdout") require.Equal(t, expectedStdout, stdout.String(), "unexpected stdout")
actualStderr := stderr.String() expectedStderr := ""
if tt.wantStderr != nil { if tt.wantStderr != nil {
testutil.RequireErrorString(t, actualStderr, tt.wantStderr(issuerCABundle, issuerEndpoint)) expectedStderr = tt.wantStderr(issuerCABundle, issuerEndpoint)
} else {
require.Empty(t, actualStderr, "unexpected stderr")
} }
require.Equal(t, expectedStderr, stderr.String(), "unexpected stderr")
}) })
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -7,27 +7,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" clientauthv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"k8s.io/client-go/tools/auth/exec" "k8s.io/client-go/tools/auth/exec"
"go.pinniped.dev/internal/here"
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals
var loginCmd = &cobra.Command{ var loginCmd = &cobra.Command{
Use: "login", Use: "login",
Short: "Authenticates with one of [oidc, static]", Short: "login",
Long: here.Doc( Long: "Login to a Pinniped server",
`Authenticates with one of [oidc, static]
Use "pinniped get kubeconfig" to generate a kubeconfig file which will include
one of these login subcommands in its configuration. The oidc and static
subcommands are not meant to be invoked directly by a user.
The oidc and static subcommands are Kubernetes client-go credential plugins
which are meant to be configured inside a kubeconfig file. (See the Kubernetes
authentication documentation for more information about client-go credential
plugins.)`,
),
SilenceUsage: true, // Do not print usage message when commands fail. SilenceUsage: true, // Do not print usage message when commands fail.
Hidden: true, // These commands are not really meant to be used directly by users, so it's confusing to have them discoverable.
} }
//nolint:gochecknoinits //nolint:gochecknoinits

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -23,7 +23,6 @@ import (
oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc" oidcapi "go.pinniped.dev/generated/latest/apis/supervisor/oidc"
"go.pinniped.dev/internal/execcredcache" "go.pinniped.dev/internal/execcredcache"
"go.pinniped.dev/internal/groupsuffix" "go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/net/phttp" "go.pinniped.dev/internal/net/phttp"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/pkg/conciergeclient" "go.pinniped.dev/pkg/conciergeclient"
@ -92,18 +91,7 @@ func oidcLoginCommand(deps oidcLoginCommandDeps) *cobra.Command {
Args: cobra.NoArgs, Args: cobra.NoArgs,
Use: "oidc --issuer ISSUER", Use: "oidc --issuer ISSUER",
Short: "Login using an OpenID Connect provider", Short: "Login using an OpenID Connect provider",
Long: here.Doc( SilenceUsage: true,
`Login using an OpenID Connect provider
Use "pinniped get kubeconfig" to generate a kubeconfig file which includes this
login command in its configuration. This login command is not meant to be
invoked directly by a user.
This login command is a Kubernetes client-go credential plugin which is meant to
be configured inside a kubeconfig file. (See the Kubernetes authentication
documentation for more information about client-go credential plugins.)`,
),
SilenceUsage: true, // do not print usage message when commands fail
} }
flags oidcLoginFlags flags oidcLoginFlags
conciergeNamespace string // unused now conciergeNamespace string // unused now

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -23,6 +23,7 @@ import (
"go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/conciergeclient" "go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes" "go.pinniped.dev/pkg/oidcclient/oidctypes"
@ -33,7 +34,7 @@ func TestLoginOIDCCommand(t *testing.T) {
testCA, err := certauthority.New("Test CA", 1*time.Hour) testCA, err := certauthority.New("Test CA", 1*time.Hour)
require.NoError(t, err) require.NoError(t, err)
tmpdir := t.TempDir() tmpdir := testutil.TempDir(t)
testCABundlePath := filepath.Join(tmpdir, "testca.pem") testCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600)) require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
@ -61,14 +62,6 @@ func TestLoginOIDCCommand(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Login using an OpenID Connect provider Login using an OpenID Connect provider
Use "pinniped get kubeconfig" to generate a kubeconfig file which includes this
login command in its configuration. This login command is not meant to be
invoked directly by a user.
This login command is a Kubernetes client-go credential plugin which is meant to
be configured inside a kubeconfig file. (See the Kubernetes authentication
documentation for more information about client-go credential plugins.)
Usage: Usage:
oidc --issuer ISSUER [flags] oidc --issuer ISSUER [flags]
@ -490,8 +483,8 @@ func TestLoginOIDCCommand(t *testing.T) {
wantOptionsCount: 4, wantOptionsCount: 4,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n", wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"expirationTimestamp":"3020-10-12T13:14:15Z","token":"test-id-token"}}` + "\n",
wantLogs: []string{ wantLogs: []string{
nowStr + ` pinniped-login cmd/login_oidc.go:243 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`, nowStr + ` pinniped-login cmd/login_oidc.go:231 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` pinniped-login cmd/login_oidc.go:263 No concierge configured, skipping token credential exchange`, nowStr + ` pinniped-login cmd/login_oidc.go:251 No concierge configured, skipping token credential exchange`,
}, },
}, },
{ {
@ -512,7 +505,7 @@ func TestLoginOIDCCommand(t *testing.T) {
"--concierge-endpoint", "https://127.0.0.1:1234/", "--concierge-endpoint", "https://127.0.0.1:1234/",
"--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()), "--concierge-ca-bundle-data", base64.StdEncoding.EncodeToString(testCA.Bundle()),
"--concierge-api-group-suffix", "some.suffix.com", "--concierge-api-group-suffix", "some.suffix.com",
"--credential-cache", t.TempDir() + "/credentials.yaml", // must specify --credential-cache or else the cache file on disk causes test pollution "--credential-cache", testutil.TempDir(t) + "/credentials.yaml", // must specify --credential-cache or else the cache file on disk causes test pollution
"--upstream-identity-provider-name", "some-upstream-name", "--upstream-identity-provider-name", "some-upstream-name",
"--upstream-identity-provider-type", "ldap", "--upstream-identity-provider-type", "ldap",
}, },
@ -520,10 +513,10 @@ func TestLoginOIDCCommand(t *testing.T) {
wantOptionsCount: 11, wantOptionsCount: 11,
wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n", wantStdout: `{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":false},"status":{"token":"exchanged-token"}}` + "\n",
wantLogs: []string{ wantLogs: []string{
nowStr + ` pinniped-login cmd/login_oidc.go:243 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`, nowStr + ` pinniped-login cmd/login_oidc.go:231 Performing OIDC login {"issuer": "test-issuer", "client id": "test-client-id"}`,
nowStr + ` pinniped-login cmd/login_oidc.go:253 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`, nowStr + ` pinniped-login cmd/login_oidc.go:241 Exchanging token for cluster credential {"endpoint": "https://127.0.0.1:1234/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
nowStr + ` pinniped-login cmd/login_oidc.go:261 Successfully exchanged token for cluster credential.`, nowStr + ` pinniped-login cmd/login_oidc.go:249 Successfully exchanged token for cluster credential.`,
nowStr + ` pinniped-login cmd/login_oidc.go:268 caching cluster credential for future use.`, nowStr + ` pinniped-login cmd/login_oidc.go:256 caching cluster credential for future use.`,
}, },
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -16,7 +16,6 @@ import (
"go.pinniped.dev/internal/execcredcache" "go.pinniped.dev/internal/execcredcache"
"go.pinniped.dev/internal/groupsuffix" "go.pinniped.dev/internal/groupsuffix"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/pkg/conciergeclient" "go.pinniped.dev/pkg/conciergeclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes" "go.pinniped.dev/pkg/oidcclient/oidctypes"
@ -59,18 +58,7 @@ func staticLoginCommand(deps staticLoginDeps) *cobra.Command {
Args: cobra.NoArgs, Args: cobra.NoArgs,
Use: "static [--token TOKEN] [--token-env TOKEN_NAME]", Use: "static [--token TOKEN] [--token-env TOKEN_NAME]",
Short: "Login using a static token", Short: "Login using a static token",
Long: here.Doc( SilenceUsage: true,
`Login using a static token
Use "pinniped get kubeconfig" to generate a kubeconfig file which includes this
login command in its configuration. This login command is not meant to be
invoked directly by a user.
This login command is a Kubernetes client-go credential plugin which is meant to
be configured inside a kubeconfig file. (See the Kubernetes authentication
documentation for more information about client-go credential plugins.)`,
),
SilenceUsage: true, // do not print usage message when commands fail
} }
flags staticLoginParams flags staticLoginParams
conciergeNamespace string // unused now conciergeNamespace string // unused now

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -21,6 +21,7 @@ import (
"go.pinniped.dev/internal/certauthority" "go.pinniped.dev/internal/certauthority"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/conciergeclient" "go.pinniped.dev/pkg/conciergeclient"
) )
@ -29,7 +30,7 @@ func TestLoginStaticCommand(t *testing.T) {
testCA, err := certauthority.New("Test CA", 1*time.Hour) testCA, err := certauthority.New("Test CA", 1*time.Hour)
require.NoError(t, err) require.NoError(t, err)
tmpdir := t.TempDir() tmpdir := testutil.TempDir(t)
testCABundlePath := filepath.Join(tmpdir, "testca.pem") testCABundlePath := filepath.Join(tmpdir, "testca.pem")
require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600)) require.NoError(t, os.WriteFile(testCABundlePath, testCA.Bundle(), 0600))
@ -55,14 +56,6 @@ func TestLoginStaticCommand(t *testing.T) {
wantStdout: here.Doc(` wantStdout: here.Doc(`
Login using a static token Login using a static token
Use "pinniped get kubeconfig" to generate a kubeconfig file which includes this
login command in its configuration. This login command is not meant to be
invoked directly by a user.
This login command is a Kubernetes client-go credential plugin which is meant to
be configured inside a kubeconfig file. (See the Kubernetes authentication
documentation for more information about client-go credential plugins.)
Usage: Usage:
static [--token TOKEN] [--token-env TOKEN_NAME] [flags] static [--token TOKEN] [--token-env TOKEN_NAME] [flags]
@ -147,7 +140,7 @@ func TestLoginStaticCommand(t *testing.T) {
Error: could not complete Concierge credential exchange: some concierge error Error: could not complete Concierge credential exchange: some concierge error
`), `),
wantLogs: []string{ wantLogs: []string{
nowStr + ` pinniped-login cmd/login_static.go:159 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`, nowStr + ` pinniped-login cmd/login_static.go:147 exchanging static token for cluster credential {"endpoint": "https://127.0.0.1/", "authenticator type": "webhook", "authenticator name": "test-authenticator"}`,
}, },
}, },
{ {

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -8,18 +8,14 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "pinniped", Use: "pinniped",
Long: here.Doc( Short: "pinniped",
`The Pinniped CLI is the client-side binary for use with Pinniped-enabled Kubernetes clusters Long: "pinniped is the client-side binary for use with Pinniped-enabled Kubernetes clusters.",
Find more information at: https://pinniped.dev`,
),
SilenceUsage: true, // do not print usage message when commands fail SilenceUsage: true, // do not print usage message when commands fail
} }

View File

@ -1,16 +1,13 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml" "k8s.io/component-base/version"
"go.pinniped.dev/internal/pversion"
) )
//nolint:gochecknoinits //nolint:gochecknoinits
@ -18,44 +15,14 @@ func init() {
rootCmd.AddCommand(newVersionCommand()) rootCmd.AddCommand(newVersionCommand())
} }
//nolint:gochecknoglobals
var (
output = new(string)
// getBuildInfo can be overwritten by tests.
getBuildInfo = pversion.Get
)
func newVersionCommand() *cobra.Command { func newVersionCommand() *cobra.Command {
c := &cobra.Command{ return &cobra.Command{
RunE: runner, 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 Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "version", Use: "version",
Short: "Print the version of this Pinniped CLI", Short: "Print the version of this Pinniped CLI",
} }
c.Flags().StringVarP(output, "output", "o", "", "one of 'yaml' or 'json'")
return c
}
func runner(cmd *cobra.Command, _ []string) error {
buildVersion := getBuildInfo()
switch {
case output == nil || *output == "":
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", buildVersion.GitVersion)
case *output == "json":
bytes, err := json.MarshalIndent(buildVersion, "", " ")
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s\n", bytes)
case *output == "yaml":
bytes, err := yaml.Marshal(buildVersion)
if err != nil {
return err
}
_, _ = fmt.Fprint(cmd.OutOrStdout(), string(bytes))
default:
return fmt.Errorf("'%s' is not a valid option for output", *output)
}
return nil
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -9,10 +9,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
apimachineryversion "k8s.io/apimachinery/pkg/version"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/pversion"
) )
var ( var (
@ -22,7 +20,7 @@ var (
Flags: Flags:
-h, --help help for version -h, --help help for version
-o, --output string one of 'yaml' or 'json'
`) `)
knownGoodHelpRegexpForVersion = here.Doc(` knownGoodHelpRegexpForVersion = here.Doc(`
@ -33,43 +31,15 @@ var (
Flags: Flags:
-h, --help help for version -h, --help help for version
-o, --output string one of 'yaml' or 'json'
`) `)
jsonRegexp = here.Doc(`{ emptyVersionRegexp = `version.Info{Major:"", Minor:"", GitVersion:".*", GitCommit:".*", GitTreeState:"", BuildDate:".*", GoVersion:".*", Compiler:".*", Platform:".*/.*"}`
"major": "\d*",
"minor": "\d*",
"gitVersion": "i am a version for json output",
"gitCommit": ".*",
"gitTreeState": ".*",
"buildDate": ".*",
"goVersion": ".*",
"compiler": ".*",
"platform": ".*/.*"
}`)
yamlRegexp = here.Doc(`buildDate: ".*"
compiler: .*
gitCommit: .*
gitTreeState: .*
gitVersion: i am a version for yaml output
goVersion: .*
major: "\d*"
minor: "\d*"
platform: .*/.*
`)
) )
func TestNewVersionCmd(t *testing.T) { func TestNewVersionCmd(t *testing.T) {
t.Cleanup(func() {
getBuildInfo = pversion.Get
})
tests := []struct { tests := []struct {
name string name string
args []string args []string
vars string
getBuildInfo func() apimachineryversion.Info
wantError bool wantError bool
wantStdoutRegexp string wantStdoutRegexp string
wantStderrRegexp string wantStderrRegexp string
@ -77,10 +47,7 @@ func TestNewVersionCmd(t *testing.T) {
{ {
name: "no flags", name: "no flags",
args: []string{}, args: []string{},
getBuildInfo: func() apimachineryversion.Info { wantStdoutRegexp: emptyVersionRegexp + "\n",
return apimachineryversion.Info{GitVersion: "v55.66.44"}
},
wantStdoutRegexp: "v55.66.44\n",
}, },
{ {
name: "help flag passed", name: "help flag passed",
@ -94,44 +61,10 @@ func TestNewVersionCmd(t *testing.T) {
wantStderrRegexp: `Error: unknown command "tuna" for "version"`, wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
wantStdoutRegexp: knownGoodUsageRegexpForVersion, wantStdoutRegexp: knownGoodUsageRegexpForVersion,
}, },
{
name: "json output",
args: []string{"--output", "json"},
getBuildInfo: func() apimachineryversion.Info {
return apimachineryversion.Info{
GitVersion: "i am a version for json output",
Platform: "a/b",
} }
},
wantStdoutRegexp: jsonRegexp,
},
{
name: "yaml output",
args: []string{"--output", "yaml"},
getBuildInfo: func() apimachineryversion.Info {
return apimachineryversion.Info{
GitVersion: "i am a version for yaml output",
Platform: "c/d",
}
},
wantStdoutRegexp: yamlRegexp,
},
{
name: "incorrect output",
args: []string{"--output", "foo"},
wantError: true,
wantStderrRegexp: `Error: 'foo' is not a valid option for output`,
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
},
}
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if tt.getBuildInfo != nil {
getBuildInfo = tt.getBuildInfo
}
cmd := newVersionCommand() cmd := newVersionCommand()
require.NotNil(t, cmd) require.NotNil(t, cmd)

View File

@ -1,4 +1,4 @@
// Copyright 2021-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2021-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package cmd package cmd
@ -48,7 +48,7 @@ func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
Args: cobra.NoArgs, // do not accept positional arguments for this command Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "whoami", Use: "whoami",
Short: "Print information about the current user", Short: "Print information about the current user",
SilenceUsage: true, // do not print usage message when commands fail SilenceUsage: true,
} }
flags := &whoamiFlags{} flags := &whoamiFlags{}

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: jwtauthenticators.authentication.concierge.pinniped.dev name: jwtauthenticators.authentication.concierge.pinniped.dev
spec: spec:
group: authentication.concierge.pinniped.dev group: authentication.concierge.pinniped.dev
@ -96,15 +97,9 @@ spec:
description: Represents the observations of the authenticator's current description: Represents the observations of the authenticator's current
state. state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -173,3 +168,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: webhookauthenticators.authentication.concierge.pinniped.dev name: webhookauthenticators.authentication.concierge.pinniped.dev
spec: spec:
group: authentication.concierge.pinniped.dev group: authentication.concierge.pinniped.dev
@ -69,15 +70,9 @@ spec:
description: Represents the observations of the authenticator's current description: Represents the observations of the authenticator's current
state. state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -146,3 +141,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: credentialissuers.config.concierge.pinniped.dev name: credentialissuers.config.concierge.pinniped.dev
spec: spec:
group: config.concierge.pinniped.dev group: config.concierge.pinniped.dev
@ -102,24 +103,6 @@ spec:
- None - None
type: string type: string
type: object type: object
tls:
description: "TLS contains information about how the Concierge
impersonation proxy should serve TLS. \n If this field is empty,
the impersonation proxy will generate its own TLS certificate."
properties:
certificateAuthorityData:
description: X.509 Certificate Authority (base64-encoded PEM
bundle). Used to advertise the CA bundle for the impersonation
proxy endpoint.
type: string
secretName:
description: SecretName is the name of a Secret in the same
namespace, of type `kubernetes.io/tls`, which contains the
TLS serving certificate for the Concierge impersonation
proxy endpoint.
minLength: 1
type: string
type: object
required: required:
- mode - mode
- service - service
@ -255,3 +238,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -1,4 +1,4 @@
#! Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. #! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
@ -134,6 +134,8 @@ spec:
#! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically #! More recently added the more unique deploymentPodLabel() so Services can select these Pods more specifically
#! without accidentally selecting any other Deployment's Pods, especially the kube cert agent Deployment's Pods. #! without accidentally selecting any other Deployment's Pods, especially the kube cert agent Deployment's Pods.
_: #@ template.replace(deploymentPodLabel()) _: #@ template.replace(deploymentPodLabel())
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
spec: spec:
securityContext: securityContext:
runAsUser: #@ data.values.run_as_user runAsUser: #@ data.values.run_as_user
@ -245,14 +247,9 @@ spec:
effect: NoSchedule effect: NoSchedule
- key: node-role.kubernetes.io/control-plane #! The new name for these nodes as of Kubernetes 1.24. - key: node-role.kubernetes.io/control-plane #! The new name for these nodes as of Kubernetes 1.24.
effect: NoSchedule effect: NoSchedule
- key: kubernetes.io/arch #! "system-cluster-critical" cannot be used outside the kube-system namespace until Kubernetes >= 1.17,
effect: NoSchedule #! so we skip setting this for now (see https://github.com/kubernetes/kubernetes/issues/60596).
operator: Equal #!priorityClassName: system-cluster-critical
value: amd64 #! Allow running on amd64 nodes.
- key: kubernetes.io/arch
effect: NoSchedule
operator: Equal
value: arm64 #! Also allow running on arm64 nodes.
#! This will help make sure our multiple pods run on different nodes, making #! This will help make sure our multiple pods run on different nodes, making
#! our deployment "more" "HA". #! our deployment "more" "HA".
affinity: affinity:

View File

@ -1,4 +1,4 @@
#! Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. #! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
@ -76,15 +76,6 @@ spec:
#! `--validate=false` flag. Note that installing via `kapp` does not complain about this validation error. #! `--validate=false` flag. Note that installing via `kapp` does not complain about this validation error.
seccompProfile: seccompProfile:
type: "RuntimeDefault" type: "RuntimeDefault"
tolerations:
- key: kubernetes.io/arch
effect: NoSchedule
operator: Equal
value: amd64 #! Allow running on amd64 nodes.
- key: kubernetes.io/arch
effect: NoSchedule
operator: Equal
value: arm64 #! Also allow running on arm64 nodes.
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: federationdomains.config.supervisor.pinniped.dev name: federationdomains.config.supervisor.pinniped.dev
spec: spec:
group: config.supervisor.pinniped.dev group: config.supervisor.pinniped.dev
@ -20,7 +21,7 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.phase - jsonPath: .status.status
name: Status name: Status
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
@ -46,264 +47,6 @@ spec:
spec: spec:
description: Spec of the OIDC provider. description: Spec of the OIDC provider.
properties: properties:
identityProviders:
description: "IdentityProviders is the list of identity providers
available for use by this FederationDomain. \n An identity provider
CR (e.g. OIDCIdentityProvider or LDAPIdentityProvider) describes
how to connect to a server, how to talk in a specific protocol for
authentication, and how to use the schema of that server/protocol
to extract a normalized user identity. Normalized user identities
include a username and a list of group names. In contrast, IdentityProviders
describes how to use that normalized identity in those Kubernetes
clusters which belong to this FederationDomain. Each entry in IdentityProviders
can be configured with arbitrary transformations on that normalized
identity. For example, a transformation can add a prefix to all
usernames to help avoid accidental conflicts when multiple identity
providers have different users with the same username (e.g. \"idp1:ryan\"
versus \"idp2:ryan\"). Each entry in IdentityProviders can also
implement arbitrary authentication rejection policies. Even though
a user was able to authenticate with the identity provider, a policy
can disallow the authentication to the Kubernetes clusters that
belong to this FederationDomain. For example, a policy could disallow
the authentication unless the user belongs to a specific group in
the identity provider. \n For backwards compatibility with versions
of Pinniped which predate support for multiple identity providers,
an empty IdentityProviders list will cause the FederationDomain
to use all available identity providers which exist in the same
namespace, but also to reject all authentication requests when there
is more than one identity provider currently defined. In this backwards
compatibility mode, the name of the identity provider resource (e.g.
the Name of an OIDCIdentityProvider resource) will be used as the
name of the identity provider in this FederationDomain. This mode
is provided to make upgrading from older versions easier. However,
instead of relying on this backwards compatibility mode, please
consider this mode to be deprecated and please instead explicitly
list the identity provider using this IdentityProviders field."
items:
description: FederationDomainIdentityProvider describes how an identity
provider is made available in this FederationDomain.
properties:
displayName:
description: DisplayName is the name of this identity provider
as it will appear to clients. This name ends up in the kubeconfig
of end users, so changing the name of an identity provider
that is in use by end users will be a disruptive change for
those users.
minLength: 1
type: string
objectRef:
description: ObjectRef is a reference to a Pinniped identity
provider resource. A valid reference is required. If the reference
cannot be resolved then the identity provider will not be
made available. Must refer to a resource of one of the Pinniped
identity provider types, e.g. OIDCIdentityProvider, LDAPIdentityProvider,
ActiveDirectoryIdentityProvider.
properties:
apiGroup:
description: APIGroup is the group for the resource being
referenced. If APIGroup is not specified, the specified
Kind must be in the core API group. For any other third-party
types, APIGroup is required.
type: string
kind:
description: Kind is the type of resource being referenced
type: string
name:
description: Name is the name of resource being referenced
type: string
required:
- kind
- name
type: object
x-kubernetes-map-type: atomic
transforms:
description: Transforms is an optional way to specify transformations
to be applied during user authentication and session refresh.
properties:
constants:
description: Constants defines constant variables and their
values which will be made available to the transform expressions.
items:
description: FederationDomainTransformsConstant defines
a constant variable and its value which will be made
available to the transform expressions. This is a union
type, and Type is the discriminator field.
properties:
name:
description: Name determines the name of the constant.
It must be a valid identifier name.
maxLength: 64
minLength: 1
pattern: ^[a-zA-Z][_a-zA-Z0-9]*$
type: string
stringListValue:
description: StringListValue should hold the value
when Type is "stringList", and is otherwise ignored.
items:
type: string
type: array
stringValue:
description: StringValue should hold the value when
Type is "string", and is otherwise ignored.
type: string
type:
description: Type determines the type of the constant,
and indicates which other field should be non-empty.
enum:
- string
- stringList
type: string
required:
- name
- type
type: object
type: array
x-kubernetes-list-map-keys:
- name
x-kubernetes-list-type: map
examples:
description: Examples can optionally be used to ensure that
the sequence of transformation expressions are working
as expected. Examples define sample input identities which
are then run through the expression list, and the results
are compared to the expected results. If any example in
this list fails, then this identity provider will not
be available for use within this FederationDomain, and
the error(s) will be added to the FederationDomain status.
This can be used to help guard against programming mistakes
in the expressions, and also act as living documentation
for other administrators to better understand the expressions.
items:
description: FederationDomainTransformsExample defines
a transform example.
properties:
expects:
description: Expects is the expected output of the
entire sequence of transforms when they are run
against the input Username and Groups.
properties:
groups:
description: Groups is the expected list of group
names after the transformations have been applied.
items:
type: string
type: array
message:
description: Message is the expected error message
of the transforms. When Rejected is true, then
Message is the expected message for the policy
which rejected the authentication attempt. When
Rejected is true and Message is blank, then
Message will be treated as the default error
message for authentication attempts which are
rejected by a policy. When Rejected is false,
then Message is the expected error message for
some other non-policy transformation error,
such as a runtime error. When Rejected is false,
there is no default expected Message.
type: string
rejected:
description: Rejected is a boolean that indicates
whether authentication is expected to be rejected
by a policy expression after the transformations
have been applied. True means that it is expected
that the authentication would be rejected. The
default value of false means that it is expected
that the authentication would not be rejected
by any policy expression.
type: boolean
username:
description: Username is the expected username
after the transformations have been applied.
type: string
type: object
groups:
description: Groups is the input list of group names.
items:
type: string
type: array
username:
description: Username is the input username.
minLength: 1
type: string
required:
- expects
- username
type: object
type: array
expressions:
description: "Expressions are an optional list of transforms
and policies to be executed in the order given during
every authentication attempt, including during every session
refresh. Each is a CEL expression. It may use the basic
CEL language as defined in https://github.com/google/cel-spec/blob/master/doc/langdef.md
plus the CEL string extensions defined in https://github.com/google/cel-go/tree/master/ext#strings.
\n The username and groups extracted from the identity
provider, and the constants defined in this CR, are available
as variables in all expressions. The username is provided
via a variable called `username` and the list of group
names is provided via a variable called `groups` (which
may be an empty list). Each user-provided constants is
provided via a variable named `strConst.varName` for string
constants and `strListConst.varName` for string list constants.
\n The only allowed types for expressions are currently
policy/v1, username/v1, and groups/v1. Each policy/v1
must return a boolean, and when it returns false, no more
expressions from the list are evaluated and the authentication
attempt is rejected. Transformations of type policy/v1
do not return usernames or group names, and therefore
cannot change the username or group names. Each username/v1
transform must return the new username (a string), which
can be the same as the old username. Transformations of
type username/v1 do not return group names, and therefore
cannot change the group names. Each groups/v1 transform
must return the new groups list (list of strings), which
can be the same as the old groups list. Transformations
of type groups/v1 do not return usernames, and therefore
cannot change the usernames. After each expression, the
new (potentially changed) username or groups get passed
to the following expression. \n Any compilation or static
type-checking failure of any expression will cause an
error status on the FederationDomain. During an authentication
attempt, any unexpected runtime evaluation errors (e.g.
division by zero) cause the authentication attempt to
fail. When all expressions evaluate successfully, then
the (potentially changed) username and group names have
been decided for that authentication attempt."
items:
description: FederationDomainTransformsExpression defines
a transform expression.
properties:
expression:
description: Expression is a CEL expression that will
be evaluated based on the Type during an authentication.
minLength: 1
type: string
message:
description: Message is only used when Type is policy/v1.
It defines an error message to be used when the
policy rejects an authentication attempt. When empty,
a default message will be used.
type: string
type:
description: Type determines the type of the expression.
It must be one of the supported types.
enum:
- policy/v1
- username/v1
- groups/v1
type: string
required:
- expression
- type
type: object
type: array
type: object
required:
- displayName
- objectRef
type: object
type: array
issuer: issuer:
description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery description: "Issuer is the OIDC Provider's issuer, per the OIDC Discovery
Metadata document, as well as the identifier that it will use for Metadata document, as well as the identifier that it will use for
@ -316,8 +59,8 @@ spec:
minLength: 1 minLength: 1
type: string type: string
tls: tls:
description: TLS specifies a secret which will contain Transport Layer description: TLS configures how this FederationDomain is served over
Security (TLS) configuration for the FederationDomain. Transport Layer Security (TLS).
properties: properties:
secretName: secretName:
description: "SecretName is an optional name of a Secret in the description: "SecretName is an optional name of a Secret in the
@ -348,86 +91,14 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
conditions: lastUpdateTime:
description: Conditions represent the observations of an FederationDomain's description: LastUpdateTime holds the time at which the Status was
current state. last updated. It is a pointer to get around some undesirable behavior
items: with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
description: "Condition contains details for one aspect of the current
state of this API Resource. --- This struct is intended for direct
use as an array at the field path .status.conditions. For example,
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
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 format: date-time
type: string type: string
message: message:
description: message is a human readable message indicating description: Message provides human-readable details about the Status.
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 FederationDomain.
enum:
- Pending
- Ready
- Error
type: string type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -444,7 +115,6 @@ spec:
TODO: Add other useful fields. apiVersion, kind, uid?' TODO: Add other useful fields. apiVersion, kind, uid?'
type: string type: string
type: object type: object
x-kubernetes-map-type: atomic
stateEncryptionKey: stateEncryptionKey:
description: StateSigningKey holds the name of the corev1.Secret description: StateSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for encrypting state parameters in which this OIDC Provider's key for encrypting state parameters
@ -455,7 +125,6 @@ spec:
TODO: Add other useful fields. apiVersion, kind, uid?' TODO: Add other useful fields. apiVersion, kind, uid?'
type: string type: string
type: object type: object
x-kubernetes-map-type: atomic
stateSigningKey: stateSigningKey:
description: StateSigningKey holds the name of the corev1.Secret description: StateSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for signing state parameters in which this OIDC Provider's key for signing state parameters
@ -466,7 +135,6 @@ spec:
TODO: Add other useful fields. apiVersion, kind, uid?' TODO: Add other useful fields. apiVersion, kind, uid?'
type: string type: string
type: object type: object
x-kubernetes-map-type: atomic
tokenSigningKey: tokenSigningKey:
description: TokenSigningKey holds the name of the corev1.Secret description: TokenSigningKey holds the name of the corev1.Secret
in which this OIDC Provider's key for signing tokens is stored. in which this OIDC Provider's key for signing tokens is stored.
@ -476,8 +144,16 @@ spec:
TODO: Add other useful fields. apiVersion, kind, uid?' TODO: Add other useful fields. apiVersion, kind, uid?'
type: string type: string
type: object type: object
x-kubernetes-map-type: atomic
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 type: object
required: required:
- spec - spec
@ -486,3 +162,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: oidcclients.config.supervisor.pinniped.dev name: oidcclients.config.supervisor.pinniped.dev
spec: spec:
group: config.supervisor.pinniped.dev group: config.supervisor.pinniped.dev
@ -128,15 +129,9 @@ spec:
description: conditions represent the observations of an OIDCClient's description: conditions represent the observations of an OIDCClient's
current state. current state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -218,3 +213,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -1,4 +1,4 @@
#! Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. #! Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
#! SPDX-License-Identifier: Apache-2.0 #! SPDX-License-Identifier: Apache-2.0
#@ load("@ytt:data", "data") #@ load("@ytt:data", "data")
@ -190,15 +190,6 @@ spec:
- name: socket - name: socket
emptyDir: {} emptyDir: {}
#@ end #@ end
tolerations:
- key: kubernetes.io/arch
effect: NoSchedule
operator: Equal
value: amd64 #! Allow running on amd64 nodes.
- key: kubernetes.io/arch
effect: NoSchedule
operator: Equal
value: arm64 #! Also allow running on arm64 nodes.
#! This will help make sure our multiple pods run on different nodes, making #! This will help make sure our multiple pods run on different nodes, making
#! our deployment "more" "HA". #! our deployment "more" "HA".
affinity: affinity:

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: activedirectoryidentityproviders.idp.supervisor.pinniped.dev name: activedirectoryidentityproviders.idp.supervisor.pinniped.dev
spec: spec:
group: idp.supervisor.pinniped.dev group: idp.supervisor.pinniped.dev
@ -106,11 +107,10 @@ spec:
description: Filter is the ActiveDirectory search filter which description: Filter is the ActiveDirectory search filter which
should be applied when searching for groups for a user. The should be applied when searching for groups for a user. The
pattern "{}" must occur in the filter at least once and will pattern "{}" must occur in the filter at least once and will
be dynamically replaced by the value of an attribute of the be dynamically replaced by the dn (distinguished name) of the
user entry found as a result of the user search. Which attribute's user entry found as a result of the user search. E.g. "member={}"
value is used to replace the placeholder(s) depends on the value or "&(objectClass=groupOfNames)(member={})". For more information
of UserAttributeForFilter. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})". about ActiveDirectory filters, see https://ldap.com/ldap-filters.
For more information about ActiveDirectory filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified, an entry, so "dn={}" cannot be used. Optional. When not specified,
the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})". the default will act as if the filter were specified as "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={})".
@ -142,20 +142,6 @@ spec:
carefully read all release notes before upgrading to ensure carefully read all release notes before upgrading to ensure
that the meaning of this field has not changed." that the meaning of this field has not changed."
type: boolean type: boolean
userAttributeForFilter:
description: UserAttributeForFilter specifies which attribute's
value from the user entry found as a result of the user search
will be used to replace the "{}" placeholder(s) in the group
search Filter. For example, specifying "uid" as the UserAttributeForFilter
while specifying "&(objectClass=posixGroup)(memberUid={})" as
the Filter would search for groups by replacing the "{}" placeholder
in the Filter with the value of the user's "uid" attribute.
Optional. When not specified, the default will act as if "dn"
were specified. For example, leaving UserAttributeForFilter
unspecified while specifying "&(objectClass=groupOfNames)(member={})"
as the Filter would search for groups by replacing the "{}"
placeholder(s) with the dn (distinguished name) of the user.
type: string
type: object type: object
host: host:
description: 'Host is the hostname of this Active Directory identity description: 'Host is the hostname of this Active Directory identity
@ -231,15 +217,9 @@ spec:
description: Represents the observations of an identity provider's description: Represents the observations of an identity provider's
current state. current state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -316,3 +296,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: ldapidentityproviders.idp.supervisor.pinniped.dev name: ldapidentityproviders.idp.supervisor.pinniped.dev
spec: spec:
group: idp.supervisor.pinniped.dev group: idp.supervisor.pinniped.dev
@ -95,16 +96,15 @@ spec:
used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com". used as the search base when searching for groups. E.g. "ou=groups,dc=example,dc=com".
When not specified, no group search will be performed and authenticated When not specified, no group search will be performed and authenticated
users will not belong to any groups from the LDAP provider. users will not belong to any groups from the LDAP provider.
Also, when not specified, the values of Filter, UserAttributeForFilter, Also, when not specified, the values of Filter and Attributes
Attributes, and SkipGroupRefresh are ignored. are ignored.
type: string type: string
filter: filter:
description: Filter is the LDAP search filter which should be description: Filter is the LDAP search filter which should be
applied when searching for groups for a user. The pattern "{}" applied when searching for groups for a user. The pattern "{}"
must occur in the filter at least once and will be dynamically must occur in the filter at least once and will be dynamically
replaced by the value of an attribute of the user entry found replaced by the dn (distinguished name) of the user entry found
as a result of the user search. Which attribute's value is used as a result of the user search. E.g. "member={}" or "&(objectClass=groupOfNames)(member={})".
to replace the placeholder(s) depends on the value of UserAttributeForFilter.
For more information about LDAP filters, see https://ldap.com/ldap-filters. For more information about LDAP filters, see https://ldap.com/ldap-filters.
Note that the dn (distinguished name) is not an attribute of Note that the dn (distinguished name) is not an attribute of
an entry, so "dn={}" cannot be used. Optional. When not specified, an entry, so "dn={}" cannot be used. Optional. When not specified,
@ -134,20 +134,6 @@ spec:
carefully read all release notes before upgrading to ensure carefully read all release notes before upgrading to ensure
that the meaning of this field has not changed." that the meaning of this field has not changed."
type: boolean type: boolean
userAttributeForFilter:
description: UserAttributeForFilter specifies which attribute's
value from the user entry found as a result of the user search
will be used to replace the "{}" placeholder(s) in the group
search Filter. For example, specifying "uid" as the UserAttributeForFilter
while specifying "&(objectClass=posixGroup)(memberUid={})" as
the Filter would search for groups by replacing the "{}" placeholder
in the Filter with the value of the user's "uid" attribute.
Optional. When not specified, the default will act as if "dn"
were specified. For example, leaving UserAttributeForFilter
unspecified while specifying "&(objectClass=groupOfNames)(member={})"
as the Filter would search for groups by replacing the "{}"
placeholder(s) with the dn (distinguished name) of the user.
type: string
type: object type: object
host: host:
description: 'Host is the hostname of this LDAP identity provider, description: 'Host is the hostname of this LDAP identity provider,
@ -228,15 +214,9 @@ spec:
description: Represents the observations of an identity provider's description: Represents the observations of an identity provider's
current state. current state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -313,3 +293,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View File

@ -3,7 +3,8 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:
controller-gen.kubebuilder.io/version: v0.13.0 controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: oidcidentityproviders.idp.supervisor.pinniped.dev name: oidcidentityproviders.idp.supervisor.pinniped.dev
spec: spec:
group: idp.supervisor.pinniped.dev group: idp.supervisor.pinniped.dev
@ -194,13 +195,11 @@ spec:
claim names as the values. These new claim names will be nested claim names as the values. These new claim names will be nested
under the top-level "additionalClaims" claim in ID tokens generated under the top-level "additionalClaims" claim in ID tokens generated
by the Supervisor when this OIDCIdentityProvider was used for by the Supervisor when this OIDCIdentityProvider was used for
user authentication. These claims will be made available to user authentication. This feature is not required for using
all clients. This feature is not required to use the Supervisor the Supervisor to provide authentication for Kubernetes clusters,
to provide authentication for Kubernetes clusters, but can be but can be used when using the Supervisor for other authentication
used when using the Supervisor for other authentication purposes. purposes. When this map is empty, the "additionalClaims" claim
When this map is empty or the upstream claims are not available, will be excluded from the ID tokens generated by the Supervisor.
the "additionalClaims" claim will be excluded from the ID tokens
generated by the Supervisor.
type: object type: object
groups: groups:
description: Groups provides the name of the ID token claim or description: Groups provides the name of the ID token claim or
@ -258,15 +257,9 @@ spec:
description: Represents the observations of an identity provider's description: Represents the observations of an identity provider's
current state. current state.
items: items:
description: "Condition contains details for one aspect of the current description: Condition status of a resource (mirrored from the metav1.Condition
state of this API Resource. --- This struct is intended for direct type added in Kubernetes 1.19). In a future API version we can
use as an array at the field path .status.conditions. For example, switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
\n type FooStatus struct{ // Represents the observations of a
foo's current state. // Known .status.conditions.type are: \"Available\",
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
// +listType=map // +listMapKey=type Conditions []metav1.Condition
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
properties: properties:
lastTransitionTime: lastTransitionTime:
description: lastTransitionTime is the last time the condition description: lastTransitionTime is the last time the condition
@ -343,3 +336,9 @@ spec:
storage: true storage: true
subresources: subresources:
status: {} status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -12,7 +12,7 @@ type JWTAuthenticatorStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
// Spec for configuring a JWT authenticator. // Spec for configuring a JWT authenticator.

View File

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

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -12,7 +12,7 @@ type WebhookAuthenticatorStatus struct {
// +patchStrategy=merge // +patchStrategy=merge
// +listType=map // +listType=map
// +listMapKey=type // +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
} }
// Spec for configuring a webhook authenticator. // Spec for configuring a webhook authenticator.

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by deepcopy-gen. DO NOT EDIT. // Code generated by deepcopy-gen. DO NOT EDIT.
@ -9,10 +9,26 @@
package v1alpha1 package v1alpha1
import ( import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Condition) DeepCopyInto(out *Condition) {
*out = *in
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
func (in *Condition) DeepCopy() *Condition {
if in == nil {
return nil
}
out := new(Condition)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) { func (in *JWTAuthenticator) DeepCopyInto(out *JWTAuthenticator) {
*out = *in *out = *in
@ -101,7 +117,7 @@ func (in *JWTAuthenticatorStatus) DeepCopyInto(out *JWTAuthenticatorStatus) {
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in)) *out = make([]Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
@ -238,7 +254,7 @@ func (in *WebhookAuthenticatorStatus) DeepCopyInto(out *WebhookAuthenticatorStat
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in)) *out = make([]Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package v1alpha1 package v1alpha1
@ -80,28 +80,6 @@ const (
ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None") ImpersonationProxyServiceTypeNone = ImpersonationProxyServiceType("None")
) )
// ImpersonationProxyTLSSpec contains information about how the Concierge impersonation proxy should
// serve TLS.
//
// If CertificateAuthorityData is not provided, the Concierge impersonation proxy will check the secret
// for a field called "ca.crt", which will be used as the CertificateAuthorityData.
//
// If neither CertificateAuthorityData nor ca.crt is provided, no CA bundle will be advertised for
// the impersonation proxy endpoint.
type ImpersonationProxyTLSSpec struct {
// X.509 Certificate Authority (base64-encoded PEM bundle).
// Used to advertise the CA bundle for the impersonation proxy endpoint.
//
// +optional
CertificateAuthorityData string `json:"certificateAuthorityData,omitempty"`
// SecretName is the name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains
// the TLS serving certificate for the Concierge impersonation proxy endpoint.
//
// +kubebuilder:validation:MinLength=1
SecretName string `json:"secretName,omitempty"`
}
// ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy. // ImpersonationProxySpec describes the intended configuration of the Concierge impersonation proxy.
type ImpersonationProxySpec struct { type ImpersonationProxySpec struct {
// Mode configures whether the impersonation proxy should be started: // Mode configures whether the impersonation proxy should be started:
@ -122,13 +100,6 @@ type ImpersonationProxySpec struct {
// //
// +optional // +optional
ExternalEndpoint string `json:"externalEndpoint,omitempty"` ExternalEndpoint string `json:"externalEndpoint,omitempty"`
// TLS contains information about how the Concierge impersonation proxy should serve TLS.
//
// If this field is empty, the impersonation proxy will generate its own TLS certificate.
//
// +optional
TLS *ImpersonationProxyTLSSpec `json:"tls,omitempty"`
} }
// ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy. // ImpersonationProxyServiceSpec describes how the Concierge should provision a Service to expose the impersonation proxy.

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by deepcopy-gen. DO NOT EDIT. // Code generated by deepcopy-gen. DO NOT EDIT.
@ -229,11 +229,6 @@ func (in *ImpersonationProxyServiceSpec) DeepCopy() *ImpersonationProxyServiceSp
func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) { func (in *ImpersonationProxySpec) DeepCopyInto(out *ImpersonationProxySpec) {
*out = *in *out = *in
in.Service.DeepCopyInto(&out.Service) in.Service.DeepCopyInto(&out.Service)
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(ImpersonationProxyTLSSpec)
**out = **in
}
return return
} }
@ -247,22 +242,6 @@ func (in *ImpersonationProxySpec) DeepCopy() *ImpersonationProxySpec {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImpersonationProxyTLSSpec) DeepCopyInto(out *ImpersonationProxyTLSSpec) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImpersonationProxyTLSSpec.
func (in *ImpersonationProxyTLSSpec) DeepCopy() *ImpersonationProxyTLSSpec {
if in == nil {
return nil
}
out := new(ImpersonationProxyTLSSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) { func (in *TokenCredentialRequestAPIInfo) DeepCopyInto(out *TokenCredentialRequestAPIInfo) {
*out = *in *out = *in

View File

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

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by conversion-gen. DO NOT EDIT. // Code generated by conversion-gen. DO NOT EDIT.
@ -11,7 +11,7 @@ package v1alpha1
import ( import (
unsafe "unsafe" unsafe "unsafe"
identity "go.pinniped.dev/generated/1.27/apis/concierge/identity" identity "go.pinniped.dev/generated/1.17/apis/concierge/identity"
conversion "k8s.io/apimachinery/pkg/conversion" conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )

View File

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

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by defaulter-gen. DO NOT EDIT. // Code generated by defaulter-gen. DO NOT EDIT.

View File

@ -6,7 +6,7 @@ package validation
import ( import (
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
identityapi "go.pinniped.dev/generated/1.28/apis/concierge/identity" identityapi "go.pinniped.dev/generated/1.17/apis/concierge/identity"
) )
func ValidateWhoAmIRequest(whoAmIRequest *identityapi.WhoAmIRequest) field.ErrorList { func ValidateWhoAmIRequest(whoAmIRequest *identityapi.WhoAmIRequest) field.ErrorList {

View File

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

View File

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

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by conversion-gen. DO NOT EDIT. // Code generated by conversion-gen. DO NOT EDIT.
@ -11,7 +11,7 @@ package v1alpha1
import ( import (
unsafe "unsafe" unsafe "unsafe"
login "go.pinniped.dev/generated/1.28/apis/concierge/login" login "go.pinniped.dev/generated/1.17/apis/concierge/login"
conversion "k8s.io/apimachinery/pkg/conversion" conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )

View File

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

View File

@ -1,7 +1,7 @@
//go:build !ignore_autogenerated //go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Code generated by defaulter-gen. DO NOT EDIT. // Code generated by defaulter-gen. DO NOT EDIT.

View File

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

9
generated/1.17/apis/go.mod generated Normal file
View File

@ -0,0 +1,9 @@
// This go.mod file is generated by ./hack/codegen.sh.
module go.pinniped.dev/generated/1.17/apis
go 1.13
require (
k8s.io/api v0.17.17
k8s.io/apimachinery v0.17.17
)

105
generated/1.17/apis/go.sum generated Normal file
View File

@ -0,0 +1,105 @@
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.17.17 h1:S+Yv5pdfvy9OG1t148zMFk3/l/VYpF1N4j5Y/q8IMdg=
k8s.io/api v0.17.17/go.mod h1:kk4nQM0EVx+BEY7o8CN5YL99CWmWEQ2a4NCak58yB6E=
k8s.io/apimachinery v0.17.17 h1:HMpFl9yqNI5G2+2WllKOe2XYLkCyaWzfXvk7SosyVko=
k8s.io/apimachinery v0.17.17/go.mod h1:T54ZSpncArE25c5r2PbUPsLeTpkPWY/ivafigSX6+xk=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU=
sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

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

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