Add spec.secretName
to OPC and handle case-insensitive hostnames
- When two different Issuers have the same host (i.e. they differ only by path) then they must have the same secretName. This is because it wouldn't make sense for there to be two different TLS certificates for one host. Find any that do not have the same secret name to put an error status on them and to avoid serving OIDC endpoints for them. The host comparison is case-insensitive. - Issuer hostnames should be treated as case-insensitive, because DNS hostnames are case-insensitive. So https://me.com and https://mE.cOm are duplicate issuers. However, paths are case-sensitive, so https://me.com/A and https://me.com/a are different issuers. Fixed this in the issuer validations and in the OIDC Manager's request router logic.
This commit is contained in:
parent
110c72a5d4
commit
25a91019c2
@ -12,9 +12,10 @@ import (
|
|||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
||||||
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
||||||
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
SameIssuerHostMustUseSameSecretOIDCProviderStatus = OIDCProviderStatus("SameIssuerHostMustUseSameSecret")
|
||||||
|
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
||||||
@ -29,6 +30,15 @@ type OIDCProviderConfigSpec struct {
|
|||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
|
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`,
|
||||||
|
// which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
// SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside
|
||||||
|
// the cluster using a LoadBalancer Service), and is not required when you would like to use only the
|
||||||
|
// HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named
|
||||||
|
// `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|
// +optional
|
||||||
|
SecretName string `json:"secretName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
||||||
|
@ -49,6 +49,17 @@ spec:
|
|||||||
for more information."
|
for more information."
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
|
secretName:
|
||||||
|
description: SecretName is an optional name of a Secret in the same
|
||||||
|
namespace, of type `kubernetes.io/tls`, which contains the TLS serving
|
||||||
|
certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
SecretName is required if you would like to use the HTTPS endpoints
|
||||||
|
(e.g. when exposing them outside the cluster using a LoadBalancer
|
||||||
|
Service), and is not required when you would like to use only the
|
||||||
|
HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS
|
||||||
|
secret must contain keys named `tls.crt` and `tls.key` that contain
|
||||||
|
the certificate and private key to use for TLS.
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- issuer
|
- issuer
|
||||||
type: object
|
type: object
|
||||||
|
1
generated/1.17/README.adoc
generated
1
generated/1.17/README.adoc
generated
@ -132,6 +132,7 @@ OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
|||||||
| Field | Description
|
| Field | Description
|
||||||
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
||||||
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
|
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ import (
|
|||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
||||||
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
||||||
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
SameIssuerHostMustUseSameSecretOIDCProviderStatus = OIDCProviderStatus("SameIssuerHostMustUseSameSecret")
|
||||||
|
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
||||||
@ -29,6 +30,15 @@ type OIDCProviderConfigSpec struct {
|
|||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
|
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`,
|
||||||
|
// which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
// SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside
|
||||||
|
// the cluster using a LoadBalancer Service), and is not required when you would like to use only the
|
||||||
|
// HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named
|
||||||
|
// `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|
// +optional
|
||||||
|
SecretName string `json:"secretName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
||||||
|
@ -397,6 +397,13 @@ func schema_117_apis_config_v1alpha1_OIDCProviderConfigSpec(ref common.Reference
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"secretName": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"issuer"},
|
Required: []string{"issuer"},
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,17 @@ spec:
|
|||||||
for more information."
|
for more information."
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
|
secretName:
|
||||||
|
description: SecretName is an optional name of a Secret in the same
|
||||||
|
namespace, of type `kubernetes.io/tls`, which contains the TLS serving
|
||||||
|
certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
SecretName is required if you would like to use the HTTPS endpoints
|
||||||
|
(e.g. when exposing them outside the cluster using a LoadBalancer
|
||||||
|
Service), and is not required when you would like to use only the
|
||||||
|
HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS
|
||||||
|
secret must contain keys named `tls.crt` and `tls.key` that contain
|
||||||
|
the certificate and private key to use for TLS.
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- issuer
|
- issuer
|
||||||
type: object
|
type: object
|
||||||
|
1
generated/1.18/README.adoc
generated
1
generated/1.18/README.adoc
generated
@ -132,6 +132,7 @@ OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
|||||||
| Field | Description
|
| Field | Description
|
||||||
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
||||||
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
|
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ import (
|
|||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
||||||
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
||||||
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
SameIssuerHostMustUseSameSecretOIDCProviderStatus = OIDCProviderStatus("SameIssuerHostMustUseSameSecret")
|
||||||
|
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
||||||
@ -29,6 +30,15 @@ type OIDCProviderConfigSpec struct {
|
|||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
|
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`,
|
||||||
|
// which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
// SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside
|
||||||
|
// the cluster using a LoadBalancer Service), and is not required when you would like to use only the
|
||||||
|
// HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named
|
||||||
|
// `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|
// +optional
|
||||||
|
SecretName string `json:"secretName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
||||||
|
@ -397,6 +397,13 @@ func schema_118_apis_config_v1alpha1_OIDCProviderConfigSpec(ref common.Reference
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"secretName": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"issuer"},
|
Required: []string{"issuer"},
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,17 @@ spec:
|
|||||||
for more information."
|
for more information."
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
|
secretName:
|
||||||
|
description: SecretName is an optional name of a Secret in the same
|
||||||
|
namespace, of type `kubernetes.io/tls`, which contains the TLS serving
|
||||||
|
certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
SecretName is required if you would like to use the HTTPS endpoints
|
||||||
|
(e.g. when exposing them outside the cluster using a LoadBalancer
|
||||||
|
Service), and is not required when you would like to use only the
|
||||||
|
HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS
|
||||||
|
secret must contain keys named `tls.crt` and `tls.key` that contain
|
||||||
|
the certificate and private key to use for TLS.
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- issuer
|
- issuer
|
||||||
type: object
|
type: object
|
||||||
|
1
generated/1.19/README.adoc
generated
1
generated/1.19/README.adoc
generated
@ -132,6 +132,7 @@ OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
|||||||
| Field | Description
|
| Field | Description
|
||||||
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
| *`issuer`* __string__ | Issuer is the OIDC Provider's issuer, per the OIDC Discovery Metadata document, as well as the identifier that it will use for the iss claim in issued JWTs. This field will also be used as the base URL for any endpoints used by the OIDC Provider (e.g., if your issuer is https://example.com/foo, then your authorization endpoint will look like https://example.com/foo/some/path/to/auth/endpoint).
|
||||||
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
See https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
|
| *`secretName`* __string__ | SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ import (
|
|||||||
type OIDCProviderStatus string
|
type OIDCProviderStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
SuccessOIDCProviderStatus = OIDCProviderStatus("Success")
|
||||||
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
DuplicateOIDCProviderStatus = OIDCProviderStatus("Duplicate")
|
||||||
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
SameIssuerHostMustUseSameSecretOIDCProviderStatus = OIDCProviderStatus("SameIssuerHostMustUseSameSecret")
|
||||||
|
InvalidOIDCProviderStatus = OIDCProviderStatus("Invalid")
|
||||||
)
|
)
|
||||||
|
|
||||||
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
// OIDCProviderConfigSpec is a struct that describes an OIDC Provider.
|
||||||
@ -29,6 +30,15 @@ type OIDCProviderConfigSpec struct {
|
|||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3 for more information.
|
||||||
// +kubebuilder:validation:MinLength=1
|
// +kubebuilder:validation:MinLength=1
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
|
// SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`,
|
||||||
|
// which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
// SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside
|
||||||
|
// the cluster using a LoadBalancer Service), and is not required when you would like to use only the
|
||||||
|
// HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named
|
||||||
|
// `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.
|
||||||
|
// +optional
|
||||||
|
SecretName string `json:"secretName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
// OIDCProviderConfigStatus is a struct that describes the actual state of an OIDC Provider.
|
||||||
|
@ -398,6 +398,13 @@ func schema_119_apis_config_v1alpha1_OIDCProviderConfigSpec(ref common.Reference
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"secretName": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "SecretName is an optional name of a Secret in the same namespace, of type `kubernetes.io/tls`, which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider. SecretName is required if you would like to use the HTTPS endpoints (e.g. when exposing them outside the cluster using a LoadBalancer Service), and is not required when you would like to use only the HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS secret must contain keys named `tls.crt` and `tls.key` that contain the certificate and private key to use for TLS.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"issuer"},
|
Required: []string{"issuer"},
|
||||||
},
|
},
|
||||||
|
@ -49,6 +49,17 @@ spec:
|
|||||||
for more information."
|
for more information."
|
||||||
minLength: 1
|
minLength: 1
|
||||||
type: string
|
type: string
|
||||||
|
secretName:
|
||||||
|
description: SecretName is an optional name of a Secret in the same
|
||||||
|
namespace, of type `kubernetes.io/tls`, which contains the TLS serving
|
||||||
|
certificate for the HTTPS endpoints served by this OIDC Provider.
|
||||||
|
SecretName is required if you would like to use the HTTPS endpoints
|
||||||
|
(e.g. when exposing them outside the cluster using a LoadBalancer
|
||||||
|
Service), and is not required when you would like to use only the
|
||||||
|
HTTP endpoints (e.g. when terminating TLS at an Ingress). The TLS
|
||||||
|
secret must contain keys named `tls.crt` and `tls.key` that contain
|
||||||
|
the certificate and private key to use for TLS.
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- issuer
|
- issuer
|
||||||
type: object
|
type: object
|
||||||
|
@ -6,6 +6,8 @@ package supervisorconfig
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/multierror"
|
"go.pinniped.dev/internal/multierror"
|
||||||
|
|
||||||
@ -71,29 +73,76 @@ func (c *oidcProviderConfigWatcherController) Sync(ctx controllerlib.Context) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make a map of issuer strings -> count of how many times we saw that issuer string.
|
||||||
|
// This will help us complain when there are duplicate issuer strings.
|
||||||
|
// Also make a helper function for forming keys into this map.
|
||||||
issuerCounts := make(map[string]int)
|
issuerCounts := make(map[string]int)
|
||||||
|
issuerURLToIssuerKey := func(issuerURL *url.URL) string {
|
||||||
|
return fmt.Sprintf("%s://%s%s", issuerURL.Scheme, strings.ToLower(issuerURL.Host), issuerURL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a map of issuer addresses -> set of unique secret names. This will help us complain when
|
||||||
|
// multiple OIDCProviderConfigs have the same issuer address (host) component but specify
|
||||||
|
// different TLS serving Secrets. Doesn't make sense to have the one address use more than one
|
||||||
|
// TLS cert. Also make a helper function for forming keys into this map.
|
||||||
|
uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool)
|
||||||
|
issuerURLToHostKey := func(issuerURL *url.URL) string {
|
||||||
|
return strings.ToLower(issuerURL.Host)
|
||||||
|
}
|
||||||
|
|
||||||
for _, opc := range all {
|
for _, opc := range all {
|
||||||
issuerCounts[opc.Spec.Issuer]++
|
issuerURL, err := url.Parse(opc.Spec.Issuer)
|
||||||
|
if err != nil {
|
||||||
|
continue // Skip url parse errors because they will be validated again below.
|
||||||
|
}
|
||||||
|
|
||||||
|
issuerCounts[issuerURLToIssuerKey(issuerURL)]++
|
||||||
|
|
||||||
|
setOfSecretNames := uniqueSecretNamesPerIssuerAddress[issuerURLToHostKey(issuerURL)]
|
||||||
|
if setOfSecretNames == nil {
|
||||||
|
setOfSecretNames = make(map[string]bool)
|
||||||
|
uniqueSecretNamesPerIssuerAddress[issuerURLToHostKey(issuerURL)] = setOfSecretNames
|
||||||
|
}
|
||||||
|
setOfSecretNames[opc.Spec.SecretName] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := multierror.New()
|
errs := multierror.New()
|
||||||
|
|
||||||
oidcProviders := make([]*provider.OIDCProvider, 0)
|
oidcProviders := make([]*provider.OIDCProvider, 0)
|
||||||
for _, opc := range all {
|
for _, opc := range all {
|
||||||
if issuerCount := issuerCounts[opc.Spec.Issuer]; issuerCount > 1 {
|
issuerURL, urlParseErr := url.Parse(opc.Spec.Issuer)
|
||||||
|
|
||||||
|
// Skip url parse errors because they will be validated below.
|
||||||
|
if urlParseErr == nil {
|
||||||
|
if issuerCount := issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 {
|
||||||
|
if err := c.updateStatus(
|
||||||
|
ctx.Context,
|
||||||
|
opc.Namespace,
|
||||||
|
opc.Name,
|
||||||
|
configv1alpha1.DuplicateOIDCProviderStatus,
|
||||||
|
"Duplicate issuer: "+opc.Spec.Issuer,
|
||||||
|
); err != nil {
|
||||||
|
errs.Add(fmt.Errorf("could not update status: %w", err))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip url parse errors because they will be validated below.
|
||||||
|
if urlParseErr == nil && len(uniqueSecretNamesPerIssuerAddress[issuerURLToHostKey(issuerURL)]) > 1 {
|
||||||
if err := c.updateStatus(
|
if err := c.updateStatus(
|
||||||
ctx.Context,
|
ctx.Context,
|
||||||
opc.Namespace,
|
opc.Namespace,
|
||||||
opc.Name,
|
opc.Name,
|
||||||
configv1alpha1.DuplicateOIDCProviderStatus,
|
configv1alpha1.SameIssuerHostMustUseSameSecretOIDCProviderStatus,
|
||||||
"Duplicate issuer: "+opc.Spec.Issuer,
|
"Issuers with the same address must use the same secretName: "+issuerURLToHostKey(issuerURL),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
errs.Add(fmt.Errorf("could not update status: %w", err))
|
errs.Add(fmt.Errorf("could not update status: %w", err))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
oidcProvider, err := provider.NewOIDCProvider(opc.Spec.Issuer)
|
oidcProvider, err := provider.NewOIDCProvider(opc.Spec.Issuer) // This validates the Issuer URL.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := c.updateStatus(
|
if err := c.updateStatus(
|
||||||
ctx.Context,
|
ctx.Context,
|
||||||
|
@ -6,6 +6,7 @@ package supervisorconfig
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@ -667,22 +668,24 @@ func TestSync(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
|
// Hostnames are case-insensitive, so consider them to be duplicates if they only differ by case.
|
||||||
|
// Paths are case-sensitive, so having a path that differs only by case makes a new issuer.
|
||||||
oidcProviderConfigDuplicate1 = &v1alpha1.OIDCProviderConfig{
|
oidcProviderConfigDuplicate1 = &v1alpha1.OIDCProviderConfig{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "duplicate1", Namespace: namespace},
|
ObjectMeta: metav1.ObjectMeta{Name: "duplicate1", Namespace: namespace},
|
||||||
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://issuer-duplicate.com"},
|
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"},
|
||||||
}
|
}
|
||||||
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigDuplicate1))
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigDuplicate1))
|
||||||
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigDuplicate1))
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigDuplicate1))
|
||||||
oidcProviderConfigDuplicate2 = &v1alpha1.OIDCProviderConfig{
|
oidcProviderConfigDuplicate2 = &v1alpha1.OIDCProviderConfig{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace},
|
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace},
|
||||||
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://issuer-duplicate.com"},
|
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://issuer-duplicate.com/a"},
|
||||||
}
|
}
|
||||||
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigDuplicate2))
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigDuplicate2))
|
||||||
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigDuplicate2))
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigDuplicate2))
|
||||||
|
|
||||||
oidcProviderConfig = &v1alpha1.OIDCProviderConfig{
|
oidcProviderConfig = &v1alpha1.OIDCProviderConfig{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace},
|
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace},
|
||||||
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://issuer-not-duplicate.com"},
|
Spec: v1alpha1.OIDCProviderConfigSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path
|
||||||
}
|
}
|
||||||
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfig))
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfig))
|
||||||
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfig))
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfig))
|
||||||
@ -715,11 +718,11 @@ func TestSync(t *testing.T) {
|
|||||||
oidcProviderConfig.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
oidcProviderConfig.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
oidcProviderConfigDuplicate1.Status.Status = v1alpha1.DuplicateOIDCProviderStatus
|
oidcProviderConfigDuplicate1.Status.Status = v1alpha1.DuplicateOIDCProviderStatus
|
||||||
oidcProviderConfigDuplicate1.Status.Message = "Duplicate issuer: https://issuer-duplicate.com"
|
oidcProviderConfigDuplicate1.Status.Message = "Duplicate issuer: https://iSSueR-duPlicAte.cOm/a"
|
||||||
oidcProviderConfigDuplicate1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
oidcProviderConfigDuplicate1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
oidcProviderConfigDuplicate2.Status.Status = v1alpha1.DuplicateOIDCProviderStatus
|
oidcProviderConfigDuplicate2.Status.Status = v1alpha1.DuplicateOIDCProviderStatus
|
||||||
oidcProviderConfigDuplicate2.Status.Message = "Duplicate issuer: https://issuer-duplicate.com"
|
oidcProviderConfigDuplicate2.Status.Message = "Duplicate issuer: https://issuer-duplicate.com/a"
|
||||||
oidcProviderConfigDuplicate2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
oidcProviderConfigDuplicate2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
expectedActions := []coretesting.Action{
|
expectedActions := []coretesting.Action{
|
||||||
@ -770,10 +773,10 @@ func TestSync(t *testing.T) {
|
|||||||
|
|
||||||
it("returns the get errors", func() {
|
it("returns the get errors", func() {
|
||||||
expectedError := here.Doc(`
|
expectedError := here.Doc(`
|
||||||
3 error(s):
|
3 error(s):
|
||||||
- could not update status: get failed: some get error
|
- could not update status: get failed: some get error
|
||||||
- could not update status: get failed: some get error
|
- could not update status: get failed: some get error
|
||||||
- could not update status: get failed: some get error`)
|
- could not update status: get failed: some get error`)
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
r.EqualError(err, expectedError)
|
r.EqualError(err, expectedError)
|
||||||
@ -804,6 +807,196 @@ func TestSync(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("there are OIDCProviderConfigs with the same issuer address using different secretNames", func() {
|
||||||
|
var (
|
||||||
|
oidcProviderConfigSameIssuerAddress1 *v1alpha1.OIDCProviderConfig
|
||||||
|
oidcProviderConfigSameIssuerAddress2 *v1alpha1.OIDCProviderConfig
|
||||||
|
oidcProviderConfigDifferentIssuerAddress *v1alpha1.OIDCProviderConfig
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL *v1alpha1.OIDCProviderConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
oidcProviderConfigSameIssuerAddress1 = &v1alpha1.OIDCProviderConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "provider1", Namespace: namespace},
|
||||||
|
Spec: v1alpha1.OIDCProviderConfigSpec{
|
||||||
|
Issuer: "https://iSSueR-duPlicAte-adDress.cOm/path1",
|
||||||
|
SecretName: "secret1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigSameIssuerAddress1))
|
||||||
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigSameIssuerAddress1))
|
||||||
|
oidcProviderConfigSameIssuerAddress2 = &v1alpha1.OIDCProviderConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "provider2", Namespace: namespace},
|
||||||
|
Spec: v1alpha1.OIDCProviderConfigSpec{
|
||||||
|
Issuer: "https://issuer-duplicate-address.com/path2",
|
||||||
|
SecretName: "secret2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigSameIssuerAddress2))
|
||||||
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigSameIssuerAddress2))
|
||||||
|
|
||||||
|
oidcProviderConfigDifferentIssuerAddress = &v1alpha1.OIDCProviderConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressProvider", Namespace: namespace},
|
||||||
|
Spec: v1alpha1.OIDCProviderConfigSpec{
|
||||||
|
Issuer: "https://issuer-not-duplicate.com",
|
||||||
|
SecretName: "secret1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigDifferentIssuerAddress))
|
||||||
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigDifferentIssuerAddress))
|
||||||
|
|
||||||
|
// Also add one with a URL that cannot be parsed to make sure that the error handling
|
||||||
|
// for the duplicate issuers and secret names are not confused by invalid URLs.
|
||||||
|
invalidIssuerURL := ":/host//path"
|
||||||
|
_, err := url.Parse(invalidIssuerURL) //nolint:staticcheck // Yes, this URL is intentionally invalid.
|
||||||
|
r.Error(err)
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL = &v1alpha1.OIDCProviderConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLProvider", Namespace: namespace},
|
||||||
|
Spec: v1alpha1.OIDCProviderConfigSpec{
|
||||||
|
Issuer: invalidIssuerURL,
|
||||||
|
SecretName: "secret1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r.NoError(pinnipedAPIClient.Tracker().Add(oidcProviderConfigWithInvalidIssuerURL))
|
||||||
|
r.NoError(opcInformerClient.Tracker().Add(oidcProviderConfigWithInvalidIssuerURL))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls the ProvidersSetter with the non-duplicate", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
nonDuplicateProvider, err := provider.NewOIDCProvider(oidcProviderConfigDifferentIssuerAddress.Spec.Issuer)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.True(providersSetter.SetProvidersWasCalled)
|
||||||
|
r.Equal(
|
||||||
|
[]*provider.OIDCProvider{
|
||||||
|
nonDuplicateProvider,
|
||||||
|
},
|
||||||
|
providersSetter.OIDCProvidersReceived,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates the statuses", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.Status = v1alpha1.SuccessOIDCProviderStatus
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.Message = "Provider successfully created"
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretOIDCProviderStatus
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Status.Message = "Issuers with the same address must use the same secretName: issuer-duplicate-address.com"
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretOIDCProviderStatus
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Status.Message = "Issuers with the same address must use the same secretName: issuer-duplicate-address.com"
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Status.Status = v1alpha1.InvalidOIDCProviderStatus
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Status.Message = `Invalid: could not parse issuer as URL: parse ":/host//path": missing protocol scheme`
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
|
expectedActions := []coretesting.Action{
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewUpdateAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress1,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewUpdateAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress2,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Namespace,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewUpdateAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Namespace,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Namespace,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewUpdateAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Namespace,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
|
||||||
|
})
|
||||||
|
|
||||||
|
when("we cannot talk to the API", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
pinnipedAPIClient.PrependReactor(
|
||||||
|
"get",
|
||||||
|
"oidcproviderconfigs",
|
||||||
|
func(_ coretesting.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, nil, errors.New("some get error")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the get errors", func() {
|
||||||
|
expectedError := here.Doc(`
|
||||||
|
4 error(s):
|
||||||
|
- could not update status: get failed: some get error
|
||||||
|
- could not update status: get failed: some get error
|
||||||
|
- could not update status: get failed: some get error
|
||||||
|
- could not update status: get failed: some get error`)
|
||||||
|
startInformersAndController()
|
||||||
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||||
|
r.EqualError(err, expectedError)
|
||||||
|
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.Status = v1alpha1.SuccessOIDCProviderStatus
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.Message = "Provider successfully created"
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||||
|
|
||||||
|
expectedActions := []coretesting.Action{
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress1.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Namespace,
|
||||||
|
oidcProviderConfigSameIssuerAddress2.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Namespace,
|
||||||
|
oidcProviderConfigDifferentIssuerAddress.Name,
|
||||||
|
),
|
||||||
|
coretesting.NewGetAction(
|
||||||
|
oidcProviderConfigGVR,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Namespace,
|
||||||
|
oidcProviderConfigWithInvalidIssuerURL.Name,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
when("there are no OIDCProviderConfigs in the informer", func() {
|
when("there are no OIDCProviderConfigs in the informer", func() {
|
||||||
it("keeps waiting for one", func() {
|
it("keeps waiting for one", func() {
|
||||||
startInformersAndController()
|
startInformersAndController()
|
||||||
|
@ -5,6 +5,7 @@ package manager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@ -53,10 +54,10 @@ func (m *Manager) SetProviders(oidcProviders ...*provider.OIDCProvider) {
|
|||||||
m.providerHandlers = make(map[string]http.Handler)
|
m.providerHandlers = make(map[string]http.Handler)
|
||||||
|
|
||||||
for _, incomingProvider := range oidcProviders {
|
for _, incomingProvider := range oidcProviders {
|
||||||
wellKnownURL := incomingProvider.IssuerHost() + "/" + incomingProvider.IssuerPath() + oidc.WellKnownEndpointPath
|
wellKnownURL := strings.ToLower(incomingProvider.IssuerHost()) + "/" + incomingProvider.IssuerPath() + oidc.WellKnownEndpointPath
|
||||||
m.providerHandlers[wellKnownURL] = discovery.NewHandler(incomingProvider.Issuer())
|
m.providerHandlers[wellKnownURL] = discovery.NewHandler(incomingProvider.Issuer())
|
||||||
|
|
||||||
jwksURL := incomingProvider.IssuerHost() + "/" + incomingProvider.IssuerPath() + oidc.JWKSEndpointPath
|
jwksURL := strings.ToLower(incomingProvider.IssuerHost()) + "/" + incomingProvider.IssuerPath() + oidc.JWKSEndpointPath
|
||||||
m.providerHandlers[jwksURL] = jwks.NewHandler(incomingProvider.Issuer(), m.dynamicJWKSProvider)
|
m.providerHandlers[jwksURL] = jwks.NewHandler(incomingProvider.Issuer(), m.dynamicJWKSProvider)
|
||||||
|
|
||||||
klog.InfoS("oidc provider manager added or updated issuer", "issuer", incomingProvider.Issuer())
|
klog.InfoS("oidc provider manager added or updated issuer", "issuer", incomingProvider.Issuer())
|
||||||
@ -85,5 +86,5 @@ func (m *Manager) findHandler(req *http.Request) http.Handler {
|
|||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
return m.providerHandlers[req.Host+"/"+req.URL.Path]
|
return m.providerHandlers[strings.ToLower(req.Host)+"/"+req.URL.Path]
|
||||||
}
|
}
|
||||||
|
@ -33,18 +33,20 @@ func TestManager(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
issuer1 := "https://example.com/some/path"
|
issuer1 := "https://example.com/some/path"
|
||||||
|
issuer1DifferentCaseHostname := "https://eXamPle.coM/some/path"
|
||||||
issuer1KeyID := "issuer1-key"
|
issuer1KeyID := "issuer1-key"
|
||||||
issuer2 := "https://example.com/some/path/more/deeply/nested/path" // note that this is a sub-path of the other issuer url
|
issuer2 := "https://example.com/some/path/more/deeply/nested/path" // note that this is a sub-path of the other issuer url
|
||||||
|
issuer2DifferentCaseHostname := "https://exAmPlE.Com/some/path/more/deeply/nested/path"
|
||||||
issuer2KeyID := "issuer2-key"
|
issuer2KeyID := "issuer2-key"
|
||||||
|
|
||||||
newGetRequest := func(url string) *http.Request {
|
newGetRequest := func(url string) *http.Request {
|
||||||
return httptest.NewRequest(http.MethodGet, url, nil)
|
return httptest.NewRequest(http.MethodGet, url, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
requireDiscoveryRequestToBeHandled := func(issuer, requestURLSuffix string) {
|
requireDiscoveryRequestToBeHandled := func(requestIssuer, requestURLSuffix, expectedIssuerInResponse string) {
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
subject.ServeHTTP(recorder, newGetRequest(issuer+oidc.WellKnownEndpointPath+requestURLSuffix))
|
subject.ServeHTTP(recorder, newGetRequest(requestIssuer+oidc.WellKnownEndpointPath+requestURLSuffix))
|
||||||
|
|
||||||
r.False(fallbackHandlerWasCalled)
|
r.False(fallbackHandlerWasCalled)
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ func TestManager(t *testing.T) {
|
|||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
// Minimal check to ensure that the right discovery endpoint was called
|
// Minimal check to ensure that the right discovery endpoint was called
|
||||||
r.Equal(issuer, parsedDiscoveryResult.Issuer)
|
r.Equal(expectedIssuerInResponse, parsedDiscoveryResult.Issuer)
|
||||||
}
|
}
|
||||||
|
|
||||||
requireJWKSRequestToBeHandled := func(requestIssuer, requestURLSuffix, expectedJWKKeyID string) {
|
requireJWKSRequestToBeHandled := func(requestIssuer, requestURLSuffix, expectedJWKKeyID string) {
|
||||||
@ -145,13 +147,23 @@ func TestManager(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("routes matching requests to the appropriate provider", func() {
|
it("routes matching requests to the appropriate provider", func() {
|
||||||
requireDiscoveryRequestToBeHandled(issuer1, "")
|
requireDiscoveryRequestToBeHandled(issuer1, "", issuer1)
|
||||||
requireDiscoveryRequestToBeHandled(issuer2, "")
|
requireDiscoveryRequestToBeHandled(issuer2, "", issuer2)
|
||||||
requireDiscoveryRequestToBeHandled(issuer2, "?some=query")
|
requireDiscoveryRequestToBeHandled(issuer2, "?some=query", issuer2)
|
||||||
|
|
||||||
|
// Hostnames are case-insensitive, so test that we can handle that.
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer1DifferentCaseHostname, "", issuer1)
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer2DifferentCaseHostname, "", issuer2)
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer2DifferentCaseHostname, "?some=query", issuer2)
|
||||||
|
|
||||||
requireJWKSRequestToBeHandled(issuer1, "", issuer1KeyID)
|
requireJWKSRequestToBeHandled(issuer1, "", issuer1KeyID)
|
||||||
requireJWKSRequestToBeHandled(issuer2, "", issuer2KeyID)
|
requireJWKSRequestToBeHandled(issuer2, "", issuer2KeyID)
|
||||||
requireJWKSRequestToBeHandled(issuer2, "?some=query", issuer2KeyID)
|
requireJWKSRequestToBeHandled(issuer2, "?some=query", issuer2KeyID)
|
||||||
|
|
||||||
|
// Hostnames are case-insensitive, so test that we can handle that.
|
||||||
|
requireJWKSRequestToBeHandled(issuer1DifferentCaseHostname, "", issuer1KeyID)
|
||||||
|
requireJWKSRequestToBeHandled(issuer2DifferentCaseHostname, "", issuer2KeyID)
|
||||||
|
requireJWKSRequestToBeHandled(issuer2DifferentCaseHostname, "?some=query", issuer2KeyID)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -170,13 +182,23 @@ func TestManager(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("still routes matching requests to the appropriate provider", func() {
|
it("still routes matching requests to the appropriate provider", func() {
|
||||||
requireDiscoveryRequestToBeHandled(issuer1, "")
|
requireDiscoveryRequestToBeHandled(issuer1, "", issuer1)
|
||||||
requireDiscoveryRequestToBeHandled(issuer2, "")
|
requireDiscoveryRequestToBeHandled(issuer2, "", issuer2)
|
||||||
requireDiscoveryRequestToBeHandled(issuer2, "?some=query")
|
requireDiscoveryRequestToBeHandled(issuer2, "?some=query", issuer2)
|
||||||
|
|
||||||
|
// Hostnames are case-insensitive, so test that we can handle that.
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer1DifferentCaseHostname, "", issuer1)
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer2DifferentCaseHostname, "", issuer2)
|
||||||
|
requireDiscoveryRequestToBeHandled(issuer2DifferentCaseHostname, "?some=query", issuer2)
|
||||||
|
|
||||||
requireJWKSRequestToBeHandled(issuer1, "", issuer1KeyID)
|
requireJWKSRequestToBeHandled(issuer1, "", issuer1KeyID)
|
||||||
requireJWKSRequestToBeHandled(issuer2, "", issuer2KeyID)
|
requireJWKSRequestToBeHandled(issuer2, "", issuer2KeyID)
|
||||||
requireJWKSRequestToBeHandled(issuer2, "?some=query", issuer2KeyID)
|
requireJWKSRequestToBeHandled(issuer2, "?some=query", issuer2KeyID)
|
||||||
|
|
||||||
|
// Hostnames are case-insensitive, so test that we can handle that.
|
||||||
|
requireJWKSRequestToBeHandled(issuer1DifferentCaseHostname, "", issuer1KeyID)
|
||||||
|
requireJWKSRequestToBeHandled(issuer2DifferentCaseHostname, "", issuer2KeyID)
|
||||||
|
requireJWKSRequestToBeHandled(issuer2DifferentCaseHostname, "?some=query", issuer2KeyID)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user