mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-08-15 11:46:12 +00:00
Merge pull request #1894 from vmware-tanzu/ben/status/webhook-authenticator
WebhookAuthenticator .Status and validation improvements
This commit is contained in:
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
@@ -596,15 +596,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
require.Len(t, gotOptions, tt.wantOptionsCount)
|
||||
|
||||
require.Equal(t, tt.wantLogs, logLines(buf.String()))
|
||||
require.Equal(t, tt.wantLogs, testutil.SplitByNewline(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func logLines(logs string) []string {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(logs), "\n")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmd
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
)
|
||||
|
||||
@@ -215,7 +216,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
|
||||
require.Equal(t, tt.wantLogs, logLines(buf.String()))
|
||||
require.Equal(t, tt.wantLogs, testutil.SplitByNewline(buf.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -150,6 +150,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -150,6 +150,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-28-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
+8
@@ -144,6 +144,14 @@ spec:
|
||||
x-kubernetes-list-map-keys:
|
||||
- type
|
||||
x-kubernetes-list-type: map
|
||||
phase:
|
||||
default: Pending
|
||||
description: Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
enum:
|
||||
- Pending
|
||||
- Ready
|
||||
- Error
|
||||
type: string
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
|
||||
Generated
+13
@@ -154,6 +154,18 @@ WebhookAuthenticator describes the configuration of a webhook authenticator.
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase"]
|
||||
==== WebhookAuthenticatorPhase (string)
|
||||
|
||||
|
||||
|
||||
.Appears In:
|
||||
****
|
||||
- xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorstatus[$$WebhookAuthenticatorStatus$$]
|
||||
****
|
||||
|
||||
|
||||
|
||||
[id="{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorspec"]
|
||||
==== WebhookAuthenticatorSpec
|
||||
|
||||
@@ -186,6 +198,7 @@ Status of a webhook authenticator.
|
||||
|===
|
||||
| Field | Description
|
||||
| *`conditions`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#condition-v1-meta[$$Condition$$] array__ | Represents the observations of the authenticator's current state.
|
||||
| *`phase`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-29-apis-concierge-authentication-v1alpha1-webhookauthenticatorphase[$$WebhookAuthenticatorPhase$$]__ | Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,10 +1,23 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
type WebhookAuthenticatorPhase string
|
||||
|
||||
const (
|
||||
// WebhookAuthenticatorPhasePending is the default phase for newly-created WebhookAuthenticator resources.
|
||||
WebhookAuthenticatorPhasePending WebhookAuthenticatorPhase = "Pending"
|
||||
|
||||
// WebhookAuthenticatorPhaseReady is the phase for an WebhookAuthenticator resource in a healthy state.
|
||||
WebhookAuthenticatorPhaseReady WebhookAuthenticatorPhase = "Ready"
|
||||
|
||||
// WebhookAuthenticatorPhaseError is the phase for an WebhookAuthenticator in an unhealthy state.
|
||||
WebhookAuthenticatorPhaseError WebhookAuthenticatorPhase = "Error"
|
||||
)
|
||||
|
||||
// Status of a webhook authenticator.
|
||||
type WebhookAuthenticatorStatus struct {
|
||||
// Represents the observations of the authenticator's current state.
|
||||
@@ -13,6 +26,10 @@ type WebhookAuthenticatorStatus struct {
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
// Phase summarizes the overall status of the WebhookAuthenticator.
|
||||
// +kubebuilder:default=Pending
|
||||
// +kubebuilder:validation:Enum=Pending;Ready;Error
|
||||
Phase WebhookAuthenticatorPhase `json:"phase,omitempty"`
|
||||
}
|
||||
|
||||
// Spec for configuring a webhook authenticator.
|
||||
|
||||
@@ -53,18 +53,21 @@ const (
|
||||
typeJWKSFetchValid = "JWKSFetchValid"
|
||||
typeAuthenticatorValid = "AuthenticatorValid"
|
||||
|
||||
reasonSuccess = "Success"
|
||||
reasonNotReady = "NotReady"
|
||||
reasonUnableToValidate = "UnableToValidate"
|
||||
reasonInvalidIssuerURL = "InvalidIssuerURL"
|
||||
reasonInvalidIssuerURLScheme = "InvalidIssuerURLScheme"
|
||||
reasonInvalidProviderJWKSURL = "InvalidProviderJWKSURL"
|
||||
reasonInvalidProviderJWKSURLScheme = "InvalidProviderJWKSURLScheme"
|
||||
reasonInvalidTLSConfiguration = "InvalidTLSConfiguration"
|
||||
reasonInvalidDiscoveryProbe = "InvalidDiscoveryProbe"
|
||||
reasonInvalidAuthenticator = "InvalidAuthenticator"
|
||||
reasonInvalidTokenSigningFailure = "InvalidTokenSigningFailure"
|
||||
reasonInvalidCouldNotFetchJWKS = "InvalidCouldNotFetchJWKS"
|
||||
reasonSuccess = "Success"
|
||||
reasonNotReady = "NotReady"
|
||||
reasonUnableToValidate = "UnableToValidate"
|
||||
reasonInvalidIssuerURL = "InvalidIssuerURL"
|
||||
reasonInvalidIssuerURLScheme = "InvalidIssuerURLScheme"
|
||||
reasonInvalidIssuerURLFragment = "InvalidIssuerURLContainsFragment"
|
||||
reasonInvalidIssuerURLQueryParams = "InvalidIssuerURLContainsQueryParams"
|
||||
reasonInvalidIssuerURLContainsWellKnownEndpoint = "InvalidIssuerURLContainsWellKnownEndpoint"
|
||||
reasonInvalidProviderJWKSURL = "InvalidProviderJWKSURL"
|
||||
reasonInvalidProviderJWKSURLScheme = "InvalidProviderJWKSURLScheme"
|
||||
reasonInvalidTLSConfiguration = "InvalidTLSConfiguration"
|
||||
reasonInvalidDiscoveryProbe = "InvalidDiscoveryProbe"
|
||||
reasonInvalidAuthenticator = "InvalidAuthenticator"
|
||||
reasonInvalidTokenSigningFailure = "InvalidTokenSigningFailure"
|
||||
reasonInvalidCouldNotFetchJWKS = "InvalidCouldNotFetchJWKS"
|
||||
|
||||
msgUnableToValidate = "unable to validate; see other conditions for details"
|
||||
|
||||
@@ -286,6 +289,39 @@ func (c *jwtCacheFillerController) validateIssuer(issuer string, conditions []*m
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(issuerURL.Path, "/.well-known/openid-configuration") {
|
||||
msg := fmt.Sprintf("spec.issuer %s cannot include path '/.well-known/openid-configuration'", issuer)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidIssuerURLContainsWellKnownEndpoint,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
if len(issuerURL.Query()) != 0 {
|
||||
msg := fmt.Sprintf("spec.issuer %s cannot include query params", issuer)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidIssuerURLQueryParams,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
if issuerURL.Fragment != "" {
|
||||
msg := fmt.Sprintf("spec.issuer %s cannot include fragment", issuer)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidIssuerURLFragment,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeIssuerURLValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
@@ -305,11 +341,12 @@ func (c *jwtCacheFillerController) validateProviderDiscovery(ctx context.Context
|
||||
})
|
||||
return nil, nil, conditions, nil
|
||||
}
|
||||
|
||||
provider, err := coreosoidc.NewProvider(ctx, issuer)
|
||||
pJSON := &providerJSON{}
|
||||
if err != nil {
|
||||
errText := "could not perform oidc discovery on provider issuer"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
msg := fmt.Sprintf("%s: %s", errText, pinnipedcontroller.TruncateMostLongErr(err))
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeDiscoveryValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
@@ -317,7 +354,7 @@ func (c *jwtCacheFillerController) validateProviderDiscovery(ctx context.Context
|
||||
Message: msg,
|
||||
})
|
||||
// resync err, may be machine or other types of non-config error
|
||||
return nil, nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
return nil, nil, conditions, fmt.Errorf("%s: %s", errText, err)
|
||||
}
|
||||
msg := "discovery performed successfully"
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
|
||||
@@ -10,10 +10,7 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -46,6 +43,7 @@ import (
|
||||
"go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/conciergetestutil"
|
||||
"go.pinniped.dev/internal/testutil/conditionstestutil"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
)
|
||||
@@ -88,6 +86,20 @@ func TestController(t *testing.T) {
|
||||
_, err := fmt.Fprintf(w, `{"issuer": "%s", "jwks_uri": "%s"}`, goodOIDCIssuerServer.URL, goodOIDCIssuerServer.URL+"/jwks.json")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
goodMux.Handle("/path/to/not/found", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
goodMux.Handle("/path/to/not/found/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, err := fmt.Fprintf(w, `<html>
|
||||
<head><title>%s</title></head>
|
||||
<body>%s</body>
|
||||
</html>`, "404 not found page", "lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz should not reach end of string")
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
goodMux.Handle("/jwks.json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ecJWK := jose.JSONWebKey{
|
||||
Key: goodECSigningKey,
|
||||
@@ -209,12 +221,12 @@ func TestController(t *testing.T) {
|
||||
someJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
someJWTAuthenticatorSpecWithUsernameClaim := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
Claims: auth1alpha1.JWTTokenClaims{
|
||||
Username: "my-custom-username-claim",
|
||||
},
|
||||
@@ -222,7 +234,7 @@ func TestController(t *testing.T) {
|
||||
someJWTAuthenticatorSpecWithGroupsClaim := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
Claims: auth1alpha1.JWTTokenClaims{
|
||||
Groups: customGroupsClaim,
|
||||
},
|
||||
@@ -248,12 +260,12 @@ func TestController(t *testing.T) {
|
||||
invalidIssuerJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://.café .com/café/café/café/coffee",
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
invalidIssuerSchemeJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "http://.café.com/café/café/café/coffee",
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
}
|
||||
|
||||
validIssuerURLButDoesNotExistJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
@@ -263,18 +275,18 @@ func TestController(t *testing.T) {
|
||||
badIssuerJWKSURIJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: badIssuerInvalidJWKSURI,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURI.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURI.TLS),
|
||||
}
|
||||
badIssuerJWKSURISchemeJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: badIssuerInvalidJWKSURIScheme,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURIScheme.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(badOIDCIssuerServerInvalidJWKSURIScheme.TLS),
|
||||
}
|
||||
|
||||
jwksFetchShouldFailJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: jwksFetchShouldFailServer.URL,
|
||||
Audience: goodAudience,
|
||||
TLS: tlsSpecFromTLSConfig(jwksFetchShouldFailServer.TLS),
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(jwksFetchShouldFailServer.TLS),
|
||||
}
|
||||
|
||||
happyReadyCondition := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
@@ -361,6 +373,39 @@ func TestController(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
sadIssuerURLValidInvalidFragment := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidIssuerURLContainsFragment",
|
||||
Message: fmt.Sprintf("spec.issuer %s cannot include fragment", issuer),
|
||||
}
|
||||
}
|
||||
|
||||
sadIssuerURLValidInvalidQueryParams := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidIssuerURLContainsQueryParams",
|
||||
Message: fmt.Sprintf("spec.issuer %s cannot include query params", issuer),
|
||||
}
|
||||
}
|
||||
|
||||
sadIssuerURLValidInvalidWellKnownEndpoint := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "IssuerURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidIssuerURLContainsWellKnownEndpoint",
|
||||
Message: fmt.Sprintf("spec.issuer %s cannot include path '/.well-known/openid-configuration'", issuer),
|
||||
}
|
||||
}
|
||||
|
||||
happyAuthenticatorValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "AuthenticatorValid",
|
||||
@@ -429,6 +474,17 @@ func TestController(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
sadDiscoveryURLValidExcessiveLongError := func(issuer string, time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "DiscoveryURLValid",
|
||||
Status: "False",
|
||||
ObservedGeneration: observedGeneration,
|
||||
LastTransitionTime: time,
|
||||
Reason: "InvalidDiscoveryProbe",
|
||||
Message: "could not perform oidc discovery on provider issuer: 404 Not Found: <html>\n\t\t \t<head><title>404 not found page</title></head>\n\t\t\t<body>lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 charact [truncated 534 chars]",
|
||||
}
|
||||
}
|
||||
|
||||
happyJWKSURLValid := func(time metav1.Time, observedGeneration int64) metav1.Condition {
|
||||
return metav1.Condition{
|
||||
Type: "JWKSURLValid",
|
||||
@@ -597,21 +653,22 @@ func TestController(t *testing.T) {
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
Name: "test-name",
|
||||
Generation: 1234,
|
||||
},
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 1233),
|
||||
[]metav1.Condition{
|
||||
// sad and unknwn will update with new statuses and timestamps
|
||||
sadReadyCondition(frozenTimeInThePast, 0),
|
||||
sadDiscoveryURLValidx509(goodIssuer, frozenTimeInThePast, 0),
|
||||
unknownAuthenticatorValid(frozenTimeInThePast, 0),
|
||||
unknownJWKSURLValid(frozenTimeInThePast, 0),
|
||||
unknownJWKSFetch(frozenTimeInThePast, 0),
|
||||
sadReadyCondition(frozenTimeInThePast, 1232),
|
||||
sadDiscoveryURLValidx509(goodIssuer, frozenTimeInThePast, 1231),
|
||||
unknownAuthenticatorValid(frozenTimeInThePast, 1232),
|
||||
unknownJWKSURLValid(frozenTimeInThePast, 1111),
|
||||
unknownJWKSFetch(frozenTimeInThePast, 1122),
|
||||
// this one will remain unchanged as it was good to begin with
|
||||
happyTLSConfigurationValidCAParsed(frozenTimeInThePast, 0),
|
||||
happyTLSConfigurationValidCAParsed(frozenTimeInThePast, 4321),
|
||||
},
|
||||
),
|
||||
Phase: "Error",
|
||||
@@ -631,15 +688,16 @@ func TestController(t *testing.T) {
|
||||
wantActions: func() []coretesting.Action {
|
||||
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
Name: "test-name",
|
||||
Generation: 1234,
|
||||
},
|
||||
Spec: *someJWTAuthenticatorSpec,
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 1234),
|
||||
[]metav1.Condition{
|
||||
// this timestamp should not have updated, it didn't change.
|
||||
happyTLSConfigurationValidCAParsed(frozenTimeInThePast, 0),
|
||||
happyTLSConfigurationValidCAParsed(frozenTimeInThePast, 1234),
|
||||
},
|
||||
),
|
||||
Phase: "Ready",
|
||||
@@ -1096,6 +1154,147 @@ func TestController(t *testing.T) {
|
||||
updateStatusAction,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
name: "validateIssuer: issuer cannot include fragment: loop will fail sync, will write failed and unknown conditions, but will not enqueue a resync due to user config error",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/#do-not-include-fragment",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantActions: func() []coretesting.Action {
|
||||
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/#do-not-include-fragment",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadIssuerURLValidInvalidFragment("https://www.example.com/foo/bar/#do-not-include-fragment", frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
unknownJWKSFetch(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
Phase: "Error",
|
||||
},
|
||||
})
|
||||
updateStatusAction.Subresource = "status"
|
||||
return []coretesting.Action{
|
||||
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
|
||||
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{}),
|
||||
updateStatusAction,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
name: "validateIssuer: issuer cannot include query params: loop will fail sync, will write failed and unknown conditions, but will not enqueue a resync due to user config error",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/?query-params=not-allowed",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantActions: func() []coretesting.Action {
|
||||
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/?query-params=not-allowed",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadIssuerURLValidInvalidQueryParams("https://www.example.com/foo/bar/?query-params=not-allowed", frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
unknownJWKSFetch(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
Phase: "Error",
|
||||
},
|
||||
})
|
||||
updateStatusAction.Subresource = "status"
|
||||
return []coretesting.Action{
|
||||
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
|
||||
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{}),
|
||||
updateStatusAction,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
name: "validateIssuer: issuer cannot include .well-known in path: loop will fail sync, will write failed and unknown conditions, but will not enqueue a resync due to user config error",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/.well-known/openid-configuration",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantActions: func() []coretesting.Action {
|
||||
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: "https://www.example.com/foo/bar/.well-known/openid-configuration",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadIssuerURLValidInvalidWellKnownEndpoint("https://www.example.com/foo/bar/.well-known/openid-configuration", frozenMetav1Now, 0),
|
||||
unknownDiscoveryURLValid(frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
unknownJWKSFetch(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
Phase: "Error",
|
||||
},
|
||||
})
|
||||
updateStatusAction.Subresource = "status"
|
||||
return []coretesting.Action{
|
||||
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
|
||||
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{}),
|
||||
updateStatusAction,
|
||||
}
|
||||
},
|
||||
}, {
|
||||
name: "validateProviderDiscovery: could not perform oidc discovery on provider issuer: loop will fail sync, will write failed and unknown conditions, and will enqueue new sync",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
@@ -1137,6 +1336,56 @@ func TestController(t *testing.T) {
|
||||
}
|
||||
},
|
||||
wantSyncLoopErr: testutil.WantExactErrorString(`could not perform oidc discovery on provider issuer: Get "` + goodIssuer + `/foo/bar/baz/shizzle/.well-known/openid-configuration": tls: failed to verify certificate: x509: certificate signed by unknown authority`),
|
||||
}, {
|
||||
name: "validateProviderDiscovery: excessively long errors truncated: loop will fail sync, will write failed and unknown conditions, and will enqueue new sync",
|
||||
jwtAuthenticators: []runtime.Object{
|
||||
&auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer + "/path/to/not/found",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
},
|
||||
},
|
||||
syncKey: controllerlib.Key{Name: "test-name"},
|
||||
wantActions: func() []coretesting.Action {
|
||||
updateStatusAction := coretesting.NewUpdateAction(jwtAuthenticatorsGVR, "", &auth1alpha1.JWTAuthenticator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-name",
|
||||
},
|
||||
Spec: auth1alpha1.JWTAuthenticatorSpec{
|
||||
Issuer: goodIssuer + "/path/to/not/found",
|
||||
Audience: goodAudience,
|
||||
TLS: conciergetestutil.TLSSpecFromTLSConfig(goodOIDCIssuerServer.TLS),
|
||||
},
|
||||
Status: auth1alpha1.JWTAuthenticatorStatus{
|
||||
Conditions: conditionstestutil.Replace(
|
||||
allHappyConditionsSuccess(goodIssuer, frozenMetav1Now, 0),
|
||||
[]metav1.Condition{
|
||||
happyIssuerURLValid(frozenMetav1Now, 0),
|
||||
sadReadyCondition(frozenMetav1Now, 0),
|
||||
sadDiscoveryURLValidExcessiveLongError(goodIssuer+"/path/to/not/found", frozenMetav1Now, 0),
|
||||
unknownAuthenticatorValid(frozenMetav1Now, 0),
|
||||
unknownJWKSURLValid(frozenMetav1Now, 0),
|
||||
unknownJWKSFetch(frozenMetav1Now, 0),
|
||||
happyTLSConfigurationValidCAParsed(frozenMetav1Now, 0),
|
||||
},
|
||||
),
|
||||
Phase: "Error",
|
||||
},
|
||||
})
|
||||
updateStatusAction.Subresource = "status"
|
||||
return []coretesting.Action{
|
||||
coretesting.NewListAction(jwtAuthenticatorsGVR, jwtAUthenticatorGVK, "", metav1.ListOptions{}),
|
||||
coretesting.NewWatchAction(jwtAuthenticatorsGVR, "", metav1.ListOptions{}),
|
||||
updateStatusAction,
|
||||
}
|
||||
},
|
||||
// not currently truncating the logged err
|
||||
wantSyncLoopErr: testutil.WantExactErrorString("could not perform oidc discovery on provider issuer: 404 Not Found: <html>\n\t\t \t<head><title>404 not found page</title></head>\n\t\t\t<body>lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz lots of text that is at least 300 characters long 0123456789 abcdefghijklmnopqrstuvwxyz should not reach end of string</body>\n\t\t</html>"),
|
||||
},
|
||||
// cannot be tested currently the way the coreos lib works.
|
||||
// the constructor requires an issuer in the payload and validates the issuer matches the actual issuer,
|
||||
@@ -1455,7 +1704,7 @@ func TestController(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
actualLogLines := logLines(log.String())
|
||||
actualLogLines := testutil.SplitByNewline(log.String())
|
||||
require.Equal(t, len(tt.wantLogs), len(actualLogLines), "log line count should be correct")
|
||||
|
||||
for logLineNum, logLine := range actualLogLines {
|
||||
@@ -1800,21 +2049,6 @@ func testTableForAuthenticateTokenTests(
|
||||
return tests
|
||||
}
|
||||
|
||||
func tlsSpecFromTLSConfig(tls *tls.Config) *auth1alpha1.TLSSpec {
|
||||
pemData := make([]byte, 0)
|
||||
for _, certificate := range tls.Certificates {
|
||||
for _, reallyCertificate := range certificate.Certificate {
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: reallyCertificate,
|
||||
})...)
|
||||
}
|
||||
}
|
||||
return &auth1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString(pemData),
|
||||
}
|
||||
}
|
||||
|
||||
func createJWT(
|
||||
t *testing.T,
|
||||
signingKey interface{},
|
||||
@@ -1868,11 +2102,3 @@ func newCacheValue(t *testing.T, spec auth1alpha1.JWTAuthenticatorSpec, wantClos
|
||||
spec: &spec,
|
||||
}
|
||||
}
|
||||
|
||||
func logLines(logs string) []string {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(logs), "\n")
|
||||
}
|
||||
|
||||
@@ -1,41 +1,83 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package webhookcachefiller implements a controller for filling an authncache.Cache with each added/updated WebhookAuthenticator.
|
||||
package webhookcachefiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
k8sauthv1beta1 "k8s.io/api/authentication/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
errorsutil "k8s.io/apimachinery/pkg/util/errors"
|
||||
k8snetutil "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
conciergeclientset "go.pinniped.dev/generated/latest/client/concierge/clientset/versioned"
|
||||
authinformers "go.pinniped.dev/generated/latest/client/concierge/informers/externalversions/authentication/v1alpha1"
|
||||
pinnipedcontroller "go.pinniped.dev/internal/controller"
|
||||
pinnipedauthenticator "go.pinniped.dev/internal/controller/authenticator"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controller/conditionsutil"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/endpointaddr"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
const (
|
||||
controllerName = "webhookcachefiller-controller"
|
||||
typeReady = "Ready"
|
||||
typeTLSConfigurationValid = "TLSConfigurationValid"
|
||||
typeWebhookConnectionValid = "WebhookConnectionValid"
|
||||
typeEndpointURLValid = "EndpointURLValid"
|
||||
typeAuthenticatorValid = "AuthenticatorValid"
|
||||
reasonSuccess = "Success"
|
||||
reasonNotReady = "NotReady"
|
||||
reasonUnableToValidate = "UnableToValidate"
|
||||
reasonUnableToCreateTempFile = "UnableToCreateTempFile"
|
||||
reasonUnableToMarshallKubeconfig = "UnableToMarshallKubeconfig"
|
||||
reasonUnableToLoadKubeconfig = "UnableToLoadKubeconfig"
|
||||
reasonUnableToInstantiateWebhook = "UnableToInstantiateWebhook"
|
||||
reasonInvalidTLSConfiguration = "InvalidTLSConfiguration"
|
||||
reasonInvalidEndpointURL = "InvalidEndpointURL"
|
||||
reasonInvalidEndpointURLScheme = "InvalidEndpointURLScheme"
|
||||
reasonUnableToDialServer = "UnableToDialServer"
|
||||
msgUnableToValidate = "unable to validate; see other conditions for details"
|
||||
)
|
||||
|
||||
// New instantiates a new controllerlib.Controller which will populate the provided authncache.Cache.
|
||||
func New(cache *authncache.Cache, webhooks authinformers.WebhookAuthenticatorInformer, log logr.Logger) controllerlib.Controller {
|
||||
func New(
|
||||
cache *authncache.Cache,
|
||||
client conciergeclientset.Interface,
|
||||
webhooks authinformers.WebhookAuthenticatorInformer,
|
||||
clock clock.Clock,
|
||||
log plog.Logger,
|
||||
tlsDialerFunc func(network string, addr string, config *tls.Config) (*tls.Conn, error),
|
||||
) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: "webhookcachefiller-controller",
|
||||
Syncer: &controller{
|
||||
cache: cache,
|
||||
webhooks: webhooks,
|
||||
log: log.WithName("webhookcachefiller-controller"),
|
||||
Name: controllerName,
|
||||
Syncer: &webhookCacheFillerController{
|
||||
cache: cache,
|
||||
client: client,
|
||||
webhooks: webhooks,
|
||||
clock: clock,
|
||||
log: log.WithName(controllerName),
|
||||
tlsDialerFunc: tlsDialerFunc,
|
||||
},
|
||||
},
|
||||
controllerlib.WithInformer(
|
||||
@@ -46,14 +88,17 @@ func New(cache *authncache.Cache, webhooks authinformers.WebhookAuthenticatorInf
|
||||
)
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
cache *authncache.Cache
|
||||
webhooks authinformers.WebhookAuthenticatorInformer
|
||||
log logr.Logger
|
||||
type webhookCacheFillerController struct {
|
||||
cache *authncache.Cache
|
||||
webhooks authinformers.WebhookAuthenticatorInformer
|
||||
client conciergeclientset.Interface
|
||||
clock clock.Clock
|
||||
log plog.Logger
|
||||
tlsDialerFunc func(network string, addr string, config *tls.Config) (*tls.Conn, error)
|
||||
}
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
func (c *webhookCacheFillerController) Sync(ctx controllerlib.Context) error {
|
||||
obj, err := c.webhooks.Lister().Get(ctx.Key.Name)
|
||||
if err != nil && errors.IsNotFound(err) {
|
||||
c.log.Info("Sync() found that the WebhookAuthenticator does not exist yet or was deleted")
|
||||
@@ -63,38 +108,82 @@ func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
return fmt.Errorf("failed to get WebhookAuthenticator %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
|
||||
}
|
||||
|
||||
webhookAuthenticator, err := newWebhookAuthenticator(&obj.Spec, os.CreateTemp, clientcmd.WriteToFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build webhook config: %w", err)
|
||||
conditions := make([]*metav1.Condition, 0)
|
||||
specCopy := obj.Spec.DeepCopy()
|
||||
var errs []error
|
||||
|
||||
certPool, pemBytes, conditions, tlsBundleOk := c.validateTLSBundle(specCopy.TLS, conditions)
|
||||
endpointHostPort, conditions, endpointOk := c.validateEndpoint(specCopy.Endpoint, conditions)
|
||||
okSoFar := tlsBundleOk && endpointOk
|
||||
conditions, tlsNegotiateErr := c.validateConnection(certPool, endpointHostPort, conditions, okSoFar)
|
||||
errs = append(errs, tlsNegotiateErr)
|
||||
okSoFar = okSoFar && tlsNegotiateErr == nil
|
||||
|
||||
webhookAuthenticator, conditions, err := newWebhookAuthenticator(
|
||||
specCopy.Endpoint,
|
||||
pemBytes,
|
||||
os.CreateTemp,
|
||||
clientcmd.WriteToFile,
|
||||
conditions,
|
||||
okSoFar,
|
||||
)
|
||||
errs = append(errs, err)
|
||||
|
||||
if !conditionsutil.HadErrorCondition(conditions) {
|
||||
c.cache.Store(authncache.Key{
|
||||
APIGroup: auth1alpha1.GroupName,
|
||||
Kind: "WebhookAuthenticator",
|
||||
Name: ctx.Key.Name,
|
||||
}, webhookAuthenticator)
|
||||
c.log.WithValues("webhook", klog.KObj(obj), "endpoint", obj.Spec.Endpoint).Info("added new webhook authenticator")
|
||||
}
|
||||
|
||||
c.cache.Store(authncache.Key{
|
||||
APIGroup: auth1alpha1.GroupName,
|
||||
Kind: "WebhookAuthenticator",
|
||||
Name: ctx.Key.Name,
|
||||
}, webhookAuthenticator)
|
||||
c.log.WithValues("webhook", klog.KObj(obj), "endpoint", obj.Spec.Endpoint).Info("added new webhook authenticator")
|
||||
return nil
|
||||
err = c.updateStatus(ctx.Context, obj, conditions)
|
||||
errs = append(errs, err)
|
||||
|
||||
// sync loop errors:
|
||||
// - should not be configuration errors. config errors a user must correct belong on the .Status
|
||||
// object. The controller simply must wait for a user to correct before running again.
|
||||
// - other errors, such as networking errors, etc. are the types of errors that should return here
|
||||
// and signal the controller to retry the sync loop. These may be corrected by machines.
|
||||
return errorsutil.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// newWebhookAuthenticator creates a webhook from the provided API server url and caBundle
|
||||
// used to validate TLS connections.
|
||||
func newWebhookAuthenticator(
|
||||
spec *auth1alpha1.WebhookAuthenticatorSpec,
|
||||
endpoint string,
|
||||
pemBytes []byte,
|
||||
tempfileFunc func(string, string) (*os.File, error),
|
||||
marshalFunc func(clientcmdapi.Config, string) error,
|
||||
) (*webhook.WebhookTokenAuthenticator, error) {
|
||||
conditions []*metav1.Condition,
|
||||
prereqOk bool,
|
||||
) (*webhook.WebhookTokenAuthenticator, []*metav1.Condition, error) {
|
||||
if !prereqOk {
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: reasonUnableToValidate,
|
||||
Message: msgUnableToValidate,
|
||||
})
|
||||
return nil, conditions, nil
|
||||
}
|
||||
temp, err := tempfileFunc("", "pinniped-webhook-kubeconfig-*")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create temporary file: %w", err)
|
||||
errText := "unable to create temporary file"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnableToCreateTempFile,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
defer func() { _ = os.Remove(temp.Name()) }()
|
||||
|
||||
cluster := &clientcmdapi.Cluster{Server: spec.Endpoint}
|
||||
_, cluster.CertificateAuthorityData, err = pinnipedauthenticator.CABundle(spec.TLS)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid TLS configuration: %w", err)
|
||||
}
|
||||
cluster := &clientcmdapi.Cluster{Server: endpoint}
|
||||
cluster.CertificateAuthorityData = pemBytes
|
||||
|
||||
kubeconfig := clientcmdapi.NewConfig()
|
||||
kubeconfig.Clusters["anonymous-cluster"] = cluster
|
||||
@@ -102,7 +191,15 @@ func newWebhookAuthenticator(
|
||||
kubeconfig.CurrentContext = "anonymous"
|
||||
|
||||
if err := marshalFunc(*kubeconfig, temp.Name()); err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal kubeconfig: %w", err)
|
||||
errText := "unable to marshal kubeconfig"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnableToMarshallKubeconfig,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
// We use v1beta1 instead of v1 since v1beta1 is more prevalent in our desired
|
||||
@@ -116,7 +213,7 @@ func newWebhookAuthenticator(
|
||||
|
||||
// We set this to nil because we would only need this to support some of the
|
||||
// custom proxy stuff used by the API server.
|
||||
var customDial net.DialFunc
|
||||
var customDial k8snetutil.DialFunc
|
||||
|
||||
// TODO refactor this code to directly construct the rest.Config
|
||||
// ideally we would keep rest config generation contained to the kubeclient package
|
||||
@@ -136,10 +233,196 @@ func newWebhookAuthenticator(
|
||||
// then use client.JSONConfig as clientConfig
|
||||
clientConfig, err := webhookutil.LoadKubeconfig(temp.Name(), customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// no unit test for this failure.
|
||||
errText := "unable to load kubeconfig"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnableToLoadKubeconfig,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
// this uses a http client that does not honor our TLS config
|
||||
// TODO fix when we pick up https://github.com/kubernetes/kubernetes/pull/106155
|
||||
return webhook.New(clientConfig, version, implicitAuds, *webhook.DefaultRetryBackoff())
|
||||
// TODO: fix when we pick up https://github.com/kubernetes/kubernetes/pull/106155
|
||||
// NOTE: looks like the above was merged on Mar 18, 2022
|
||||
webhookA, err := webhook.New(clientConfig, version, implicitAuds, *webhook.DefaultRetryBackoff())
|
||||
if err != nil {
|
||||
// no unit test for this failure.
|
||||
errText := "unable to instantiate webhook"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnableToInstantiateWebhook,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
msg := "authenticator initialized"
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeAuthenticatorValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return webhookA, conditions, nil
|
||||
}
|
||||
|
||||
func (c *webhookCacheFillerController) validateConnection(certPool *x509.CertPool, endpointHostPort *endpointaddr.HostPort, conditions []*metav1.Condition, prereqOk bool) ([]*metav1.Condition, error) {
|
||||
if !prereqOk {
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeWebhookConnectionValid,
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: reasonUnableToValidate,
|
||||
Message: msgUnableToValidate,
|
||||
})
|
||||
return conditions, nil
|
||||
}
|
||||
|
||||
conn, err := c.tlsDialerFunc("tcp", endpointHostPort.Endpoint(), &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
// If certPool is nil then RootCAs will be set to nil and TLS will use the host's root CA set automatically.
|
||||
RootCAs: certPool,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errText := "cannot dial server"
|
||||
msg := fmt.Sprintf("%s: %s", errText, err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeWebhookConnectionValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnableToDialServer,
|
||||
Message: msg,
|
||||
})
|
||||
return conditions, fmt.Errorf("%s: %w", errText, err)
|
||||
}
|
||||
|
||||
// this error should never be significant
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
c.log.Error("error closing dialer", err)
|
||||
}
|
||||
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeWebhookConnectionValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: "tls verified",
|
||||
})
|
||||
return conditions, nil
|
||||
}
|
||||
|
||||
func (c *webhookCacheFillerController) validateTLSBundle(tlsSpec *auth1alpha1.TLSSpec, conditions []*metav1.Condition) (*x509.CertPool, []byte, []*metav1.Condition, bool) {
|
||||
rootCAs, pemBytes, err := pinnipedauthenticator.CABundle(tlsSpec)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: %s", "invalid TLS configuration", err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeTLSConfigurationValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidTLSConfiguration,
|
||||
Message: msg,
|
||||
})
|
||||
return rootCAs, pemBytes, conditions, false
|
||||
}
|
||||
msg := "successfully parsed specified CA bundle"
|
||||
if rootCAs == nil {
|
||||
msg = "no CA bundle specified"
|
||||
}
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeTLSConfigurationValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: msg,
|
||||
})
|
||||
return rootCAs, pemBytes, conditions, true
|
||||
}
|
||||
|
||||
func (c *webhookCacheFillerController) validateEndpoint(endpoint string, conditions []*metav1.Condition) (*endpointaddr.HostPort, []*metav1.Condition, bool) {
|
||||
endpointURL, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: %s", "spec.endpoint URL cannot be parsed", err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeEndpointURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidEndpointURL,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
// handles empty string and other issues as well.
|
||||
if endpointURL.Scheme != "https" {
|
||||
msg := fmt.Sprintf("spec.endpoint URL %s has invalid scheme, require 'https'", endpoint)
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeEndpointURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidEndpointURLScheme,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
endpointHostPort, err := endpointaddr.ParseFromURL(endpointURL, 443)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s: %s", "spec.endpoint URL is not valid", err.Error())
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeEndpointURLValid,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidEndpointURL,
|
||||
Message: msg,
|
||||
})
|
||||
return nil, conditions, false
|
||||
}
|
||||
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeEndpointURLValid,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: "endpoint is a valid URL",
|
||||
})
|
||||
return &endpointHostPort, conditions, true
|
||||
}
|
||||
|
||||
func (c *webhookCacheFillerController) updateStatus(
|
||||
ctx context.Context,
|
||||
original *auth1alpha1.WebhookAuthenticator,
|
||||
conditions []*metav1.Condition,
|
||||
) error {
|
||||
updated := original.DeepCopy()
|
||||
|
||||
if conditionsutil.HadErrorCondition(conditions) {
|
||||
updated.Status.Phase = auth1alpha1.WebhookAuthenticatorPhaseError
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeReady,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonNotReady,
|
||||
Message: "the WebhookAuthenticator is not ready: see other conditions for details",
|
||||
})
|
||||
} else {
|
||||
updated.Status.Phase = auth1alpha1.WebhookAuthenticatorPhaseReady
|
||||
conditions = append(conditions, &metav1.Condition{
|
||||
Type: typeReady,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: reasonSuccess,
|
||||
Message: "the WebhookAuthenticator is ready",
|
||||
})
|
||||
}
|
||||
|
||||
_ = conditionsutil.MergeConfigConditions(
|
||||
conditions,
|
||||
original.Generation,
|
||||
&updated.Status.Conditions,
|
||||
plog.New().WithName(controllerName),
|
||||
metav1.NewTime(c.clock.Now()),
|
||||
)
|
||||
|
||||
if equality.Semantic.DeepEqual(original, updated) {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := c.client.AuthenticationV1alpha1().WebhookAuthenticators().UpdateStatus(ctx, updated, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,7 +66,7 @@ func mergeIDPCondition(existing *[]metav1.Condition, new *metav1.Condition) bool
|
||||
return false
|
||||
}
|
||||
|
||||
// MergeConfigConditions merges conditions into conditionsToUpdate. If returns true if it merged any error conditions.
|
||||
// MergeConfigConditions merges conditions into conditionsToUpdate. It returns true if it merged any error conditions.
|
||||
func MergeConfigConditions(conditions []*metav1.Condition, observedGeneration int64, conditionsToUpdate *[]metav1.Condition, log plog.MinLogger, now metav1.Time) bool {
|
||||
hadErrorCondition := false
|
||||
for i := range conditions {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -1085,7 +1084,7 @@ func TestAgentController(t *testing.T) {
|
||||
allAllowedErrors = append(allAllowedErrors, tt.alsoAllowUndesiredDistinctErrors...)
|
||||
assert.Subsetf(t, allAllowedErrors, actualErrors, "actual errors contained additional error(s) which is not expected by the test")
|
||||
|
||||
assert.Equal(t, tt.wantDistinctLogs, deduplicate(logLines(buf.String())), "unexpected logs")
|
||||
assert.Equal(t, tt.wantDistinctLogs, deduplicate(testutil.SplitByNewline(buf.String())), "unexpected logs")
|
||||
|
||||
// Assert on all actions that happened to deployments.
|
||||
var actualDeploymentActionVerbs []string
|
||||
@@ -1128,14 +1127,6 @@ func TestAgentController(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func logLines(logs string) []string {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(logs), "\n")
|
||||
}
|
||||
|
||||
func TestMergeLabelsAndAnnotations(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package oidcupstreamwatcher implements a controller which watches OIDCIdentityProviders.
|
||||
@@ -342,7 +342,7 @@ func (c *oidcWatcherController) validateIssuer(ctx context.Context, upstream *v1
|
||||
Type: typeOIDCDiscoverySucceeded,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonUnreachable,
|
||||
Message: fmt.Sprintf("failed to perform OIDC discovery against %q:\n%s", upstream.Spec.Issuer, truncateMostLongErr(err)),
|
||||
Message: fmt.Sprintf("failed to perform OIDC discovery against %q:\n%s", upstream.Spec.Issuer, pinnipedcontroller.TruncateMostLongErr(err)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ func (c *oidcWatcherController) validateIssuer(ctx context.Context, upstream *v1
|
||||
Type: typeOIDCDiscoverySucceeded,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reasonInvalidResponse,
|
||||
Message: fmt.Sprintf("failed to unmarshal OIDC discovery response from %q:\n%s", upstream.Spec.Issuer, truncateMostLongErr(err)),
|
||||
Message: fmt.Sprintf("failed to unmarshal OIDC discovery response from %q:\n%s", upstream.Spec.Issuer, pinnipedcontroller.TruncateMostLongErr(err)),
|
||||
}
|
||||
}
|
||||
if additionalDiscoveryClaims.RevocationEndpoint != "" {
|
||||
@@ -473,18 +473,6 @@ func computeScopes(additionalScopes []string) []string {
|
||||
return set.List()
|
||||
}
|
||||
|
||||
func truncateMostLongErr(err error) string {
|
||||
const max = 300
|
||||
msg := err.Error()
|
||||
|
||||
// always log oidc and x509 errors completely
|
||||
if len(msg) <= max || strings.Contains(msg, "oidc:") || strings.Contains(msg, "x509:") {
|
||||
return msg
|
||||
}
|
||||
|
||||
return msg[:max] + fmt.Sprintf(" [truncated %d chars]", len(msg)-max)
|
||||
}
|
||||
|
||||
func validateHTTPSURL(maybeHTTPSURL, endpointType, reason string) (*url.URL, *metav1.Condition) {
|
||||
parsedURL, err := url.Parse(maybeHTTPSURL)
|
||||
if err != nil {
|
||||
@@ -492,7 +480,7 @@ func validateHTTPSURL(maybeHTTPSURL, endpointType, reason string) (*url.URL, *me
|
||||
Type: typeOIDCDiscoverySucceeded,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: reason,
|
||||
Message: fmt.Sprintf("failed to parse %s URL: %v", endpointType, truncateMostLongErr(err)),
|
||||
Message: fmt.Sprintf("failed to parse %s URL: %v", endpointType, pinnipedcontroller.TruncateMostLongErr(err)),
|
||||
}
|
||||
}
|
||||
if parsedURL.Scheme != "https" {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TruncateMostLongErr(err error) string {
|
||||
const max = 300
|
||||
msg := err.Error()
|
||||
|
||||
// always log oidc and x509 errors completely
|
||||
if len(msg) <= max || strings.Contains(msg, "oidc:") || strings.Contains(msg, "x509:") {
|
||||
return msg
|
||||
}
|
||||
|
||||
return msg[:max] + fmt.Sprintf(" [truncated %d chars]", len(msg)-max)
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
package controllermanager
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -236,8 +237,11 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
|
||||
WithController(
|
||||
webhookcachefiller.New(
|
||||
c.AuthenticatorCache,
|
||||
client.PinnipedConcierge,
|
||||
informers.pinniped.Authentication().V1alpha1().WebhookAuthenticators(),
|
||||
plog.Logr(), //nolint:staticcheck // old controller with lots of log statements
|
||||
clock.RealClock{},
|
||||
plog.New(),
|
||||
tls.Dial,
|
||||
),
|
||||
singletonWorker,
|
||||
).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package endpointaddr implements parsing and validation of "<host>[:<port>]" strings for Pinniped APIs.
|
||||
@@ -7,7 +7,9 @@ package endpointaddr
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
)
|
||||
@@ -38,7 +40,7 @@ func (h *HostPort) Endpoint() string {
|
||||
// - "<IPv4>:<port>" (IPv4 address with port)
|
||||
// - "[<IPv6>]:<port>" (IPv6 address with port, brackets are required)
|
||||
//
|
||||
// If the input does not not specify a port number, then defaultPort will be used.
|
||||
// If the input does not specify a port number, then defaultPort will be used.
|
||||
func Parse(endpoint string, defaultPort uint16) (HostPort, error) {
|
||||
// Try parsing it both with and without an implicit port 443 at the end.
|
||||
host, port, err := net.SplitHostPort(endpoint)
|
||||
@@ -69,3 +71,28 @@ func Parse(endpoint string, defaultPort uint16) (HostPort, error) {
|
||||
|
||||
return HostPort{Host: host, Port: uint16(integerPort)}, nil
|
||||
}
|
||||
|
||||
// ParseFromURL wraps Parse but specifically takes a url.URL instead of an endpoint string.
|
||||
// ParseFromURL differs from Parse in that IPv6 addresses must be wrapped in brackets
|
||||
// when used in a URL (even when used without a port).
|
||||
//
|
||||
// If the input does not specify a port number, then defaultPort will be used.
|
||||
//
|
||||
// The RFC for literal IPv6 addresses in URLs indicates that brackets
|
||||
// - must be used when a port is provided
|
||||
// - should be used when a port is not provided, but does not indicate "must"
|
||||
// See https://datatracker.ietf.org/doc/html/rfc2732#section-2
|
||||
//
|
||||
// However, the Golang docs make it clear that IPv6 addresses must be wrapped
|
||||
// in brackets when used in a URL.
|
||||
// See https://pkg.go.dev/net/url#URL
|
||||
//
|
||||
// Note that ParseFromURL returns a HostPort which has an Endpoint() method which
|
||||
// will return a properly constructed URL with brackets when appropriate.
|
||||
func ParseFromURL(u *url.URL, defaultPort uint16) (HostPort, error) {
|
||||
host := u.Host
|
||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||
host = strings.TrimPrefix(strings.TrimSuffix(host, "]"), "[")
|
||||
}
|
||||
return Parse(host, defaultPort)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package endpointaddr
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@@ -180,3 +182,170 @@ func TestParse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFromURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
input string
|
||||
defaultPort uint16
|
||||
expectErr string
|
||||
expect HostPort
|
||||
// HostPort.Endpoint() returns a properly constructed endpoint. The normalization provided by ParseFromURL()
|
||||
// expects that the resulting HostPort.Endpoint() will be called to normalize several special cases, especially
|
||||
// for IPv6.
|
||||
expectEndpoint string
|
||||
}{
|
||||
// First set of valid passthrough tests to Parse()
|
||||
// Matches the above test table, minus any test that would not url.Parse(input) properly
|
||||
{
|
||||
name: "plain IPv4",
|
||||
input: "http://127.0.0.1",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "127.0.0.1", Port: 443},
|
||||
expectEndpoint: "127.0.0.1:443",
|
||||
},
|
||||
{
|
||||
name: "IPv4 with port",
|
||||
input: "http://127.0.0.1:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "127.0.0.1", Port: 8443},
|
||||
expectEndpoint: "127.0.0.1:8443",
|
||||
},
|
||||
{
|
||||
name: "IPv4 in brackets with port",
|
||||
input: "http://[127.0.0.1]:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "127.0.0.1", Port: 8443},
|
||||
expectEndpoint: "127.0.0.1:8443",
|
||||
},
|
||||
{
|
||||
name: "IPv4 as IPv6 in brackets with port",
|
||||
input: "http://[::127.0.0.1]:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "::127.0.0.1", Port: 8443},
|
||||
expectEndpoint: "[::127.0.0.1]:8443",
|
||||
},
|
||||
{
|
||||
name: "IPv6 with port",
|
||||
input: "http://[2001:db8::ffff]:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "2001:db8::ffff", Port: 8443},
|
||||
expectEndpoint: "[2001:db8::ffff]:8443",
|
||||
},
|
||||
{
|
||||
name: "plain hostname",
|
||||
input: "http://host.example.com",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "host.example.com", Port: 443},
|
||||
expectEndpoint: "host.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "plain hostname with dash",
|
||||
input: "http://host-dev.example.com",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "host-dev.example.com", Port: 443},
|
||||
expectEndpoint: "host-dev.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "hostname with port",
|
||||
input: "http://host.example.com:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "host.example.com", Port: 8443},
|
||||
expectEndpoint: "host.example.com:8443",
|
||||
},
|
||||
{
|
||||
name: "hostname in brackets with port",
|
||||
input: "http://[host.example.com]:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "host.example.com", Port: 8443},
|
||||
expectEndpoint: "host.example.com:8443",
|
||||
},
|
||||
{
|
||||
name: "hostname without dots",
|
||||
input: "http://localhost",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "localhost", Port: 443},
|
||||
expectEndpoint: "localhost:443",
|
||||
},
|
||||
{
|
||||
name: "hostname and port without dots",
|
||||
input: "http://localhost:8443",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "localhost", Port: 8443},
|
||||
expectEndpoint: "localhost:8443",
|
||||
},
|
||||
{
|
||||
name: "http://invalid empty string",
|
||||
input: "",
|
||||
defaultPort: 443,
|
||||
expectErr: `host "" is not a valid hostname or IP address`,
|
||||
},
|
||||
{
|
||||
name: "invalid host with underscores",
|
||||
input: "http://___.example.com:1234",
|
||||
defaultPort: 443,
|
||||
expectErr: `host "___.example.com" is not a valid hostname or IP address`,
|
||||
},
|
||||
{
|
||||
name: "invalid host with uppercase",
|
||||
input: "http://HOST.EXAMPLE.COM",
|
||||
defaultPort: 443,
|
||||
expectErr: `host "HOST.EXAMPLE.COM" is not a valid hostname or IP address`,
|
||||
},
|
||||
// new tests for new functionality
|
||||
{
|
||||
name: "IPv6 with brackets but without port will strip brackets to create HostPort{}, which will add brackets when HostPort.Endpoint() is called",
|
||||
input: "http://[2001:db8::ffff]",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "2001:db8::ffff", Port: 443},
|
||||
expectEndpoint: "[2001:db8::ffff]:443",
|
||||
},
|
||||
{
|
||||
name: "IPv6 without brackets and without port will create HostPort{}, which will add brackets when HostPort.Endpoint() is called",
|
||||
input: "http://2001:db8::1234",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "2001:db8::1234", Port: 443},
|
||||
expectEndpoint: "[2001:db8::1234]:443",
|
||||
},
|
||||
{
|
||||
name: "IPv6 without brackets and without port with path create HostPort{}, which will add brackets when HostPort.Endpoint() is called",
|
||||
input: "https://0:0:0:0:0:0:0:1/some/fake/path",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "0:0:0:0:0:0:0:1", Port: 443},
|
||||
expectEndpoint: "[0:0:0:0:0:0:0:1]:443",
|
||||
},
|
||||
{
|
||||
name: "IPv6 with mismatched leading bracket will err on bracket",
|
||||
input: "https://[[::1]/some/fake/path",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "[[::1]", Port: 443},
|
||||
expectEndpoint: "[[::1]:443",
|
||||
expectErr: `address [[::1]:443: unexpected '[' in address`,
|
||||
},
|
||||
{
|
||||
name: "IPv6 with mismatched trailing brackets will err on port",
|
||||
input: "https://[::1]]/some/fake/path",
|
||||
defaultPort: 443,
|
||||
expect: HostPort{Host: "[::1]]", Port: 443},
|
||||
expectEndpoint: "[::1]]:443",
|
||||
expectErr: `address [::1]]:443: missing port in address`,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
urlToProcess, err := url.Parse(tt.input)
|
||||
require.NoError(t, err, "ParseFromURL expects a valid url.URL, parse errors here are not valuable")
|
||||
|
||||
got, err := ParseFromURL(urlToProcess, tt.defaultPort)
|
||||
if tt.expectErr == "" {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expect, got)
|
||||
assert.Equal(t, tt.expectEndpoint, got.Endpoint())
|
||||
} else {
|
||||
assert.EqualError(t, err, tt.expectErr)
|
||||
assert.Equal(t, HostPort{}, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package conciergetestutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
)
|
||||
|
||||
func TLSSpecFromTLSConfig(tls *tls.Config) *auth1alpha1.TLSSpec {
|
||||
pemData := make([]byte, 0)
|
||||
for _, certificate := range tls.Certificates {
|
||||
// this is the public part of the certificate, the private is the certificate.PrivateKey
|
||||
for _, reallyCertificate := range certificate.Certificate {
|
||||
pemData = append(pemData, pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: reallyCertificate,
|
||||
})...)
|
||||
}
|
||||
}
|
||||
return &auth1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: base64.StdEncoding.EncodeToString(pemData),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package testutil
|
||||
|
||||
import "strings"
|
||||
|
||||
func SplitByNewline(lineToSplit string) []string {
|
||||
if len(lineToSplit) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return strings.Split(strings.TrimSpace(lineToSplit), "\n")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package integration
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
conciergescheme "go.pinniped.dev/internal/concierge/scheme"
|
||||
"go.pinniped.dev/pkg/oidcclient"
|
||||
@@ -42,7 +43,7 @@ func TestCLIGetKubeconfigStaticToken_Parallel(t *testing.T) {
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancelFunc()
|
||||
|
||||
authenticator := testlib.CreateTestWebhookAuthenticator(ctx, t)
|
||||
authenticator := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, v1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
|
||||
// Build pinniped CLI.
|
||||
pinnipedExe := testlib.PinnipedCLIPath(t)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package integration
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
@@ -83,7 +84,7 @@ func TestAPIServingCertificateAutoCreationAndRotation_Disruptive(t *testing.T) {
|
||||
|
||||
// Create a testWebhook so we have a legitimate authenticator to pass to the
|
||||
// TokenCredentialRequest API.
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t)
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, v1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
|
||||
// Get the initial auto-generated version of the Secret.
|
||||
secret, err := kubeClient.CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, defaultServingCertResourceName, metav1.GetOptions{})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
||||
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package integration
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
"go.pinniped.dev/internal/here"
|
||||
"go.pinniped.dev/pkg/conciergeclient"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
@@ -58,7 +59,7 @@ func TestClient(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
webhook := testlib.CreateTestWebhookAuthenticator(ctx, t)
|
||||
webhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, v1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
|
||||
// Use an invalid certificate/key to validate that the ServerVersion API fails like we assume.
|
||||
invalidClient := testlib.NewClientsetWithCertAndKey(t, testCert, testKey)
|
||||
|
||||
@@ -60,8 +60,10 @@ func TestSuccessfulCredentialRequest_Browser(t *testing.T) {
|
||||
token func(t *testing.T) (token string, username string, groups []string)
|
||||
}{
|
||||
{
|
||||
name: "webhook",
|
||||
authenticator: testlib.CreateTestWebhookAuthenticator,
|
||||
name: "webhook",
|
||||
authenticator: func(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
|
||||
return testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, auth1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
},
|
||||
token: func(t *testing.T) (string, string, []string) {
|
||||
return testlib.IntegrationEnv(t).TestUser.Token, env.TestUser.ExpectedUsername, env.TestUser.ExpectedGroups
|
||||
},
|
||||
@@ -148,7 +150,7 @@ func TestFailedCredentialRequestWhenTheRequestIsValidButTheTokenDoesNotAuthentic
|
||||
// TokenCredentialRequest API.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t)
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, auth1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
|
||||
response, err := testlib.CreateTokenCredentialRequest(context.Background(), t,
|
||||
loginv1alpha1.TokenCredentialRequestSpec{Token: "not a good token", Authenticator: testWebhook},
|
||||
@@ -169,7 +171,7 @@ func TestCredentialRequest_ShouldFailWhenRequestDoesNotIncludeToken_Parallel(t *
|
||||
// TokenCredentialRequest API.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t)
|
||||
testWebhook := testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, auth1alpha1.WebhookAuthenticatorPhaseReady)
|
||||
|
||||
response, err := testlib.CreateTokenCredentialRequest(context.Background(), t,
|
||||
loginv1alpha1.TokenCredentialRequestSpec{Token: "", Authenticator: testWebhook},
|
||||
|
||||
@@ -61,6 +61,7 @@ import (
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
conciergev1alpha "go.pinniped.dev/generated/latest/apis/concierge/config/v1alpha1"
|
||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/login/v1alpha1"
|
||||
@@ -120,7 +121,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
||||
// Create a WebhookAuthenticator and prepare a TokenCredentialRequestSpec using the authenticator for use later.
|
||||
credentialRequestSpecWithWorkingCredentials := loginv1alpha1.TokenCredentialRequestSpec{
|
||||
Token: env.TestUser.Token,
|
||||
Authenticator: testlib.CreateTestWebhookAuthenticator(ctx, t),
|
||||
Authenticator: testlib.CreateTestWebhookAuthenticator(ctx, t, &testlib.IntegrationEnv(t).TestWebhook, v1alpha1.WebhookAuthenticatorPhaseReady),
|
||||
}
|
||||
|
||||
// The address of the ClusterIP service that points at the impersonation proxy's port (used when there is no load balancer).
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
"go.pinniped.dev/test/testlib"
|
||||
)
|
||||
|
||||
func TestConciergeWebhookAuthenticatorStatus_Parallel(t *testing.T) {
|
||||
testEnv := testlib.IntegrationEnv(t)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
caBundleSomePivotalCA := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
spec func() *v1alpha1.WebhookAuthenticatorSpec
|
||||
initialPhase v1alpha1.WebhookAuthenticatorPhase
|
||||
finalConditions []metav1.Condition
|
||||
run func(t *testing.T)
|
||||
}{
|
||||
{
|
||||
name: "Basic test to see if the WebhookAuthenticator wakes up or not.",
|
||||
spec: func() *v1alpha1.WebhookAuthenticatorSpec {
|
||||
return &testlib.IntegrationEnv(t).TestWebhook
|
||||
},
|
||||
initialPhase: v1alpha1.WebhookAuthenticatorPhaseReady,
|
||||
finalConditions: allSuccessfulWebhookAuthenticatorConditions(),
|
||||
},
|
||||
{
|
||||
name: "valid spec with invalid CA in TLS config will result in a WebhookAuthenticator that is not ready",
|
||||
spec: func() *v1alpha1.WebhookAuthenticatorSpec {
|
||||
caBundleString := "invalid base64-encoded data"
|
||||
webhookSpec := testEnv.TestWebhook.DeepCopy()
|
||||
webhookSpec.TLS = &v1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: caBundleString,
|
||||
}
|
||||
return webhookSpec
|
||||
},
|
||||
initialPhase: v1alpha1.WebhookAuthenticatorPhaseError,
|
||||
finalConditions: replaceSomeConditions(
|
||||
allSuccessfulWebhookAuthenticatorConditions(),
|
||||
[]metav1.Condition{
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: "False",
|
||||
Reason: "NotReady",
|
||||
Message: "the WebhookAuthenticator is not ready: see other conditions for details",
|
||||
}, {
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "Unknown",
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; see other conditions for details",
|
||||
}, {
|
||||
Type: "TLSConfigurationValid",
|
||||
Status: "False",
|
||||
Reason: "InvalidTLSConfiguration",
|
||||
Message: "invalid TLS configuration: illegal base64 data at input byte 7",
|
||||
}, {
|
||||
Type: "WebhookConnectionValid",
|
||||
Status: "Unknown",
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; see other conditions for details",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "valid spec with valid CA in TLS config but does not match issuer server will result in a WebhookAuthenticator that is not ready",
|
||||
spec: func() *v1alpha1.WebhookAuthenticatorSpec {
|
||||
webhookSpec := testEnv.TestWebhook.DeepCopy()
|
||||
webhookSpec.TLS = &v1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: caBundleSomePivotalCA,
|
||||
}
|
||||
return webhookSpec
|
||||
},
|
||||
initialPhase: v1alpha1.WebhookAuthenticatorPhaseError,
|
||||
finalConditions: replaceSomeConditions(
|
||||
allSuccessfulWebhookAuthenticatorConditions(),
|
||||
[]metav1.Condition{
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: "False",
|
||||
Reason: "NotReady",
|
||||
Message: "the WebhookAuthenticator is not ready: see other conditions for details",
|
||||
}, {
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "Unknown",
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; see other conditions for details",
|
||||
}, {
|
||||
Type: "WebhookConnectionValid",
|
||||
Status: "False",
|
||||
Reason: "UnableToDialServer",
|
||||
Message: "cannot dial server: tls: failed to verify certificate: x509: certificate signed by unknown authority",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "invalid with unresponsive endpoint will result in a WebhookAuthenticator that is not ready",
|
||||
spec: func() *v1alpha1.WebhookAuthenticatorSpec {
|
||||
webhookSpec := testEnv.TestWebhook.DeepCopy()
|
||||
webhookSpec.TLS = &v1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: caBundleSomePivotalCA,
|
||||
}
|
||||
webhookSpec.Endpoint = "https://127.0.0.1:443/some-fake-endpoint"
|
||||
return webhookSpec
|
||||
},
|
||||
initialPhase: v1alpha1.WebhookAuthenticatorPhaseError,
|
||||
finalConditions: replaceSomeConditions(
|
||||
allSuccessfulWebhookAuthenticatorConditions(),
|
||||
[]metav1.Condition{
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: "False",
|
||||
Reason: "NotReady",
|
||||
Message: "the WebhookAuthenticator is not ready: see other conditions for details",
|
||||
}, {
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "Unknown",
|
||||
Reason: "UnableToValidate",
|
||||
Message: "unable to validate; see other conditions for details",
|
||||
}, {
|
||||
Type: "WebhookConnectionValid",
|
||||
Status: "False",
|
||||
Reason: "UnableToDialServer",
|
||||
Message: "cannot dial server: dial tcp 127.0.0.1:443: connect: connection refused",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
webhookAuthenticator := testlib.CreateTestWebhookAuthenticator(
|
||||
ctx,
|
||||
t,
|
||||
tt.spec(),
|
||||
tt.initialPhase)
|
||||
|
||||
testlib.WaitForWebhookAuthenticatorStatusConditions(
|
||||
ctx, t,
|
||||
webhookAuthenticator.Name,
|
||||
tt.finalConditions)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConciergeWebhookAuthenticatorCRDValidations_Parallel(t *testing.T) {
|
||||
env := testlib.IntegrationEnv(t)
|
||||
webhookAuthenticatorClient := testlib.NewConciergeClientset(t).AuthenticationV1alpha1().WebhookAuthenticators()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
objectMeta := testlib.ObjectMetaWithRandomName(t, "webhook-authenticator")
|
||||
tests := []struct {
|
||||
name string
|
||||
webhookAuthenticator *v1alpha1.WebhookAuthenticator
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "endpoint can not be empty string",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "",
|
||||
},
|
||||
},
|
||||
wantErr: `WebhookAuthenticator.authentication.concierge.` + env.APIGroupSuffix + ` "` + objectMeta.Name + `" is invalid: ` +
|
||||
`spec.endpoint: Invalid value: "": spec.endpoint in body should be at least 1 chars long`,
|
||||
},
|
||||
{
|
||||
name: "endpoint must be https",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: objectMeta,
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "http://www.example.com",
|
||||
},
|
||||
},
|
||||
wantErr: `WebhookAuthenticator.authentication.concierge.` + env.APIGroupSuffix + ` "` + objectMeta.Name + `" is invalid: ` +
|
||||
`spec.endpoint: Invalid value: "http://www.example.com": spec.endpoint in body should match '^https://'`,
|
||||
},
|
||||
{
|
||||
name: "minimum valid authenticator",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: testlib.ObjectMetaWithRandomName(t, "webhook"),
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://localhost/webhook-isnt-actually-here",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authenticator can have empty TLS block",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: testlib.ObjectMetaWithRandomName(t, "webhook"),
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://localhost/webhook-isnt-actually-here",
|
||||
TLS: &v1alpha1.TLSSpec{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid authenticator can have empty TLS CertificateAuthorityData",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"),
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://localhost/webhook-isnt-actually-here",
|
||||
TLS: &v1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// since the CRD validations do not assess fitness of the value provided
|
||||
name: "valid authenticator can have TLS CertificateAuthorityData string that is an invalid certificate",
|
||||
webhookAuthenticator: &v1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: testlib.ObjectMetaWithRandomName(t, "jwtauthenticator"),
|
||||
Spec: v1alpha1.WebhookAuthenticatorSpec{
|
||||
Endpoint: "https://localhost/webhook-isnt-actually-here",
|
||||
TLS: &v1alpha1.TLSSpec{
|
||||
CertificateAuthorityData: "pretend-this-is-a-certificate",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, createErr := webhookAuthenticatorClient.Create(ctx, tt.webhookAuthenticator, metav1.CreateOptions{})
|
||||
|
||||
t.Cleanup(func() {
|
||||
// delete if it exists
|
||||
delErr := webhookAuthenticatorClient.Delete(ctx, tt.webhookAuthenticator.Name, metav1.DeleteOptions{})
|
||||
if !errors.IsNotFound(delErr) {
|
||||
require.NoError(t, delErr)
|
||||
}
|
||||
})
|
||||
|
||||
if tt.wantErr != "" {
|
||||
wantErr := tt.wantErr
|
||||
require.EqualError(t, createErr, wantErr)
|
||||
} else {
|
||||
require.NoError(t, createErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func allSuccessfulWebhookAuthenticatorConditions() []metav1.Condition {
|
||||
return []metav1.Condition{{
|
||||
Type: "AuthenticatorValid",
|
||||
Status: "True",
|
||||
Reason: "Success",
|
||||
Message: "authenticator initialized",
|
||||
},
|
||||
{
|
||||
Type: "EndpointURLValid",
|
||||
Status: "True",
|
||||
Reason: "Success",
|
||||
Message: "endpoint is a valid URL",
|
||||
},
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: "True",
|
||||
Reason: "Success",
|
||||
Message: "the WebhookAuthenticator is ready",
|
||||
},
|
||||
{
|
||||
Type: "TLSConfigurationValid",
|
||||
Status: "True",
|
||||
Reason: "Success",
|
||||
Message: "successfully parsed specified CA bundle",
|
||||
},
|
||||
{
|
||||
Type: "WebhookConnectionValid",
|
||||
Status: "True",
|
||||
Reason: "Success",
|
||||
Message: "tls verified",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -438,7 +438,7 @@ func TestGetAPIResourceList(t *testing.T) { //nolint:gocyclo // each t.Run is pr
|
||||
}
|
||||
|
||||
// manually update this value whenever you add additional fields to an API resource and then run the generator
|
||||
totalExpectedAPIFields := 260
|
||||
totalExpectedAPIFields := 261
|
||||
|
||||
// Because we are parsing text from `kubectl explain` and because the format of that text can change
|
||||
// over time, make a rudimentary assertion that this test exercised the whole tree of all fields of all
|
||||
|
||||
+51
-3
@@ -171,9 +171,12 @@ func NewKubeclient(t *testing.T, config *rest.Config) *kubeclient.Client {
|
||||
// CreateTestWebhookAuthenticator creates and returns a test WebhookAuthenticator in $PINNIPED_TEST_CONCIERGE_NAMESPACE, which will be
|
||||
// automatically deleted at the end of the current test's lifetime. It returns a corev1.TypedLocalObjectReference which
|
||||
// describes the test webhook authenticator within the test namespace.
|
||||
func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.TypedLocalObjectReference {
|
||||
func CreateTestWebhookAuthenticator(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
webhookSpec *auth1alpha1.WebhookAuthenticatorSpec,
|
||||
expectedStatus auth1alpha1.WebhookAuthenticatorPhase) corev1.TypedLocalObjectReference {
|
||||
t.Helper()
|
||||
testEnv := IntegrationEnv(t)
|
||||
|
||||
client := NewConciergeClientset(t)
|
||||
webhooks := client.AuthenticationV1alpha1().WebhookAuthenticators()
|
||||
@@ -183,7 +186,7 @@ func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.Ty
|
||||
|
||||
webhook, err := webhooks.Create(createContext, &auth1alpha1.WebhookAuthenticator{
|
||||
ObjectMeta: testObjectMeta(t, "webhook"),
|
||||
Spec: testEnv.TestWebhook,
|
||||
Spec: *webhookSpec,
|
||||
}, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "could not create test WebhookAuthenticator")
|
||||
t.Logf("created test WebhookAuthenticator %s", webhook.Name)
|
||||
@@ -197,6 +200,10 @@ func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.Ty
|
||||
require.NoErrorf(t, err, "could not cleanup test WebhookAuthenticator %s/%s", webhook.Namespace, webhook.Name)
|
||||
})
|
||||
|
||||
if expectedStatus != "" {
|
||||
WaitForWebhookAuthenticatorStatusPhase(ctx, t, webhook.Name, expectedStatus)
|
||||
}
|
||||
|
||||
return corev1.TypedLocalObjectReference{
|
||||
APIGroup: &auth1alpha1.SchemeGroupVersion.Group,
|
||||
Kind: "WebhookAuthenticator",
|
||||
@@ -204,6 +211,47 @@ func CreateTestWebhookAuthenticator(ctx context.Context, t *testing.T) corev1.Ty
|
||||
}
|
||||
}
|
||||
|
||||
func WaitForWebhookAuthenticatorStatusPhase(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
webhookName string,
|
||||
expectPhase auth1alpha1.WebhookAuthenticatorPhase) {
|
||||
t.Helper()
|
||||
webhookAuthenticatorClientSet := NewConciergeClientset(t).AuthenticationV1alpha1().WebhookAuthenticators()
|
||||
|
||||
RequireEventuallyf(t, func(requireEventually *require.Assertions) {
|
||||
webhookA, err := webhookAuthenticatorClientSet.Get(ctx, webhookName, metav1.GetOptions{})
|
||||
requireEventually.NoError(err)
|
||||
requireEventually.Equalf(expectPhase, webhookA.Status.Phase, "actual status conditions were: %#v", webhookA.Status.Conditions)
|
||||
}, 60*time.Second, 1*time.Second, "expected the WebhookAuthenticator to have status %q", expectPhase)
|
||||
}
|
||||
|
||||
func WaitForWebhookAuthenticatorStatusConditions(ctx context.Context, t *testing.T, webhookName string, expectConditions []metav1.Condition) {
|
||||
t.Helper()
|
||||
webhookClient := NewConciergeClientset(t).AuthenticationV1alpha1().WebhookAuthenticators()
|
||||
RequireEventuallyf(t, func(requireEventually *require.Assertions) {
|
||||
fd, err := webhookClient.Get(ctx, webhookName, metav1.GetOptions{})
|
||||
requireEventually.NoError(err)
|
||||
|
||||
requireEventually.Lenf(fd.Status.Conditions, len(expectConditions),
|
||||
"wanted status conditions: %#v", expectConditions)
|
||||
|
||||
for i, wantCond := range expectConditions {
|
||||
actualCond := fd.Status.Conditions[i]
|
||||
|
||||
// This is a cheat to avoid needing to make equality assertions on these fields.
|
||||
requireEventually.NotZero(actualCond.LastTransitionTime)
|
||||
wantCond.LastTransitionTime = actualCond.LastTransitionTime
|
||||
requireEventually.NotZero(actualCond.ObservedGeneration)
|
||||
wantCond.ObservedGeneration = actualCond.ObservedGeneration
|
||||
|
||||
requireEventually.Equalf(wantCond, actualCond,
|
||||
"wanted status conditions: %#v\nactual status conditions were: %#v\nnot equal at index %d",
|
||||
expectConditions, fd.Status.Conditions, i)
|
||||
}
|
||||
}, 60*time.Second, 1*time.Second, "wanted WebhookAuthenticator conditions")
|
||||
}
|
||||
|
||||
// CreateTestJWTAuthenticatorForCLIUpstream creates and returns a test JWTAuthenticator which will be automatically
|
||||
// deleted at the end of the current test's lifetime.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user