Skip to content

WIP: AUTH-543: OIDC/OAuth resource configuration #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e096a11
common: add helper func to determine whether OIDC is enabled on KAS pods
liouk Nov 13, 2024
7ba38b8
operator: set number of replicas of workloads with a custom wrapper
liouk Nov 20, 2024
50646a7
deployment: bypass precondition checks if OIDC is available
liouk Nov 20, 2024
fae7129
endpointaccessible: disable controller checks upon specific conditions
liouk Nov 13, 2024
7505c22
proxyconfig: disable proxy checks when external OIDC config is available
liouk Nov 14, 2024
a3ed6d7
ingressnodesavailable: disable checks when external OIDC config is av…
liouk Nov 14, 2024
018e8f0
ingressstate: disable checks when external OIDC config is available
liouk Nov 14, 2024
435990f
readiness: disable checks when external OIDC config is available
liouk Nov 14, 2024
7b646ba
oauthclientscontroller: remove operands when external OIDC config is …
liouk Nov 14, 2024
587c31d
metadata: remove operands if external OIDC config is available
liouk Nov 14, 2024
687641d
operator: configure static resources that depend on oidc
liouk Nov 20, 2024
98c5092
routercerts: remove operands if external OIDC config is available
liouk Nov 15, 2024
e80d6a2
serviceca: remove operands if external OIDC config is available
liouk Nov 15, 2024
a24c190
payload: remove operands if external OIDC config is available
liouk Nov 15, 2024
fab276c
customroute: remove operands and clear custom route ingress status if…
liouk Nov 18, 2024
86647ec
operator: enable or disable API services depending on whether OIDC is…
liouk Nov 20, 2024
37841a3
controllers: add switchable informer controller
liouk Nov 22, 2024
0a81630
oauthclientscontroller: use a switched informer for oauthclients
liouk Nov 22, 2024
7a11fe6
fixup! readiness: disable checks when external OIDC config is available
liouk Nov 29, 2024
ad63ec6
fixup! customroute: remove operands and clear custom route ingress st…
liouk Nov 29, 2024
a379d88
fixup! controllers: add switchable informer controller
liouk Dec 3, 2024
641d1dd
fixup! oauthclientscontroller: use a switched informer for oauthclients
liouk Dec 3, 2024
de9f965
fixup! common: add helper func to determine whether OIDC is enabled o…
liouk Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions pkg/controllers/common/external_oidc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package common

import (
"fmt"
"strings"

configv1 "github.com/openshift/api/config/v1"
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/sets"
corelistersv1 "k8s.io/client-go/listers/core/v1"
)

// ExternalOIDCConfigAvailable checks the kubeapiservers/cluster resource for KAS pod
// rollout status; it returns true if auth type is OIDC, all KAS pods are currently on a revision
// that includes the structured auth-config ConfigMap, and the KAS args include the respective
// arg that enables usage of the structured auth-config. It returns false otherwise.
func ExternalOIDCConfigAvailable(authLister configv1listers.AuthenticationLister, kasLister operatorv1listers.KubeAPIServerLister, cmLister corelistersv1.ConfigMapLister) (bool, error) {
auth, err := authLister.Get("cluster")
if err != nil {
return false, err
}

if auth.Spec.Type != configv1.AuthenticationTypeOIDC {
return false, nil
}

kas, err := kasLister.Get("cluster")
if err != nil {
return false, err
}

observedRevisions := sets.New[int32]()
for _, nodeStatus := range kas.Status.NodeStatuses {
observedRevisions.Insert(nodeStatus.CurrentRevision)
}

if observedRevisions.Len() == 0 {
return false, nil
}

for _, revision := range observedRevisions.UnsortedList() {
// ensure every observed revision includes an auth-config revisioned configmap
_, err := cmLister.ConfigMaps("openshift-kube-apiserver").Get(fmt.Sprintf("auth-config-%d", revision))
if errors.IsNotFound(err) {
return false, nil
} else if err != nil {
return false, err
}

// every observed revision includes a copy of the KAS config configmap
cm, err := cmLister.ConfigMaps("openshift-kube-apiserver").Get(fmt.Sprintf("config-%d", revision))
if err != nil {
return false, err
}

// ensure the KAS config of every observed revision contains the appropriate CLI arg for OIDC
// but not the respective ones for OAuth
if !strings.Contains(cm.Data["config.yaml"], `"oauthMetadataFile":""`) ||
strings.Contains(cm.Data["config.yaml"], `"authentication-token-webhook-config-file":`) ||
!strings.Contains(cm.Data["config.yaml"], `"authentication-config":["/etc/kubernetes/static-pod-resources/configmaps/auth-config/auth-config.json"]`) {
return false, nil
}
}

return true, nil
}
317 changes: 317 additions & 0 deletions pkg/controllers/common/external_oidc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
package common

import (
"testing"

configv1 "github.com/openshift/api/config/v1"
operatorv1 "github.com/openshift/api/operator/v1"
configv1listers "github.com/openshift/client-go/config/listers/config/v1"
operatorv1listers "github.com/openshift/client-go/operator/listers/operator/v1"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corelistersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
)

const (
kasConfigJSONWithOIDC = `"spec":{"apiServerArguments":{"authentication-config":["/etc/kubernetes/static-pod-resources/configmaps/auth-config/auth-config.json"]},"oauthMetadataFile":""}`
kasConfigJSONWithoutOIDC = `"spec":{"apiServerArguments":{"authentication-token-webhook-config-file":["/etc/kubernetes/static-pod-resources/secrets/webhook-authenticator/kubeConfig"]},"oauthMetadataFile":"/etc/kubernetes/static-pod-resources/configmaps/oauth-metadata/oauthMetadata"}`
)

func TestExternalOIDCConfigAvailable(t *testing.T) {
for _, tt := range []struct {
name string
configMaps []*corev1.ConfigMap
authType configv1.AuthenticationType
nodeStatuses []operatorv1.NodeStatus
expectAvailable bool
expectError bool
}{
{
name: "no node statuses observed",
authType: configv1.AuthenticationTypeOIDC,
expectAvailable: false,
expectError: false,
},
{
name: "oidc disabled, no rollout",
configMaps: []*corev1.ConfigMap{cm("config-10", "config.yaml", kasConfigJSONWithoutOIDC)},
authType: configv1.AuthenticationTypeIntegratedOAuth,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 10},
{CurrentRevision: 10},
{CurrentRevision: 10},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc getting enabled, rollout in progress",
configMaps: []*corev1.ConfigMap{
cm("config-10", "config.yaml", kasConfigJSONWithoutOIDC),
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 10, TargetRevision: 11},
{CurrentRevision: 10},
{CurrentRevision: 10},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc getting enabled, rollout in progress, one node ready",
configMaps: []*corev1.ConfigMap{
cm("config-10", "config.yaml", kasConfigJSONWithoutOIDC),
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 11},
{CurrentRevision: 10, TargetRevision: 11},
{CurrentRevision: 10},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc getting enabled, rollout in progress, two nodes ready",
configMaps: []*corev1.ConfigMap{
cm("config-10", "config.yaml", kasConfigJSONWithoutOIDC),
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 11},
{CurrentRevision: 11},
{CurrentRevision: 10, TargetRevision: 11},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc got enabled",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 11},
{CurrentRevision: 11},
{CurrentRevision: 11},
},
expectAvailable: true,
expectError: false,
},
{
name: "oidc enabled, rollout in progress",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 11, TargetRevision: 12},
{CurrentRevision: 11},
{CurrentRevision: 11},
},
expectAvailable: true,
expectError: false,
},
{
name: "oidc enabled, rollout in progress, one node ready",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 12},
{CurrentRevision: 11, TargetRevision: 12},
{CurrentRevision: 11},
},
expectAvailable: true,
expectError: false,
},
{
name: "oidc enabled, rollout in progress, two nodes ready",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 12},
{CurrentRevision: 12},
{CurrentRevision: 11, TargetRevision: 12},
},
expectAvailable: true,
expectError: false,
},
{
name: "oidc still enabled",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeOIDC,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 12},
{CurrentRevision: 12},
{CurrentRevision: 12},
},
expectAvailable: true,
expectError: false,
},
{
name: "oidc getting disabled, rollout in progress",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("config-13", "config.yaml", kasConfigJSONWithoutOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeIntegratedOAuth,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 12, TargetRevision: 13},
{CurrentRevision: 12},
{CurrentRevision: 12},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc getting disabled, rollout in progress, one node ready",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("config-13", "config.yaml", kasConfigJSONWithoutOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeIntegratedOAuth,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 13},
{CurrentRevision: 12, TargetRevision: 13},
{CurrentRevision: 12},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc getting disabled, rollout in progress, two nodes ready",
authType: configv1.AuthenticationTypeIntegratedOAuth,
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-13", "config.yaml", kasConfigJSONWithoutOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 13},
{CurrentRevision: 13},
{CurrentRevision: 12, TargetRevision: 13},
},
expectAvailable: false,
expectError: false,
},
{
name: "oidc got disabled",
configMaps: []*corev1.ConfigMap{
cm("config-11", "config.yaml", kasConfigJSONWithOIDC),
cm("config-12", "config.yaml", kasConfigJSONWithOIDC),
cm("config-13", "config.yaml", kasConfigJSONWithoutOIDC),
cm("auth-config-11", "", ""),
cm("auth-config-12", "", ""),
},
authType: configv1.AuthenticationTypeIntegratedOAuth,
nodeStatuses: []operatorv1.NodeStatus{
{CurrentRevision: 13},
{CurrentRevision: 13},
{CurrentRevision: 13},
},
expectAvailable: false,
expectError: false,
},
} {
t.Run(tt.name, func(t *testing.T) {

cmIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
for _, cm := range tt.configMaps {
cmIndexer.Add(cm)
}

kasIndexer := cache.NewIndexer(func(obj interface{}) (string, error) {
return "cluster", nil
}, cache.Indexers{})

kasIndexer.Add(&operatorv1.KubeAPIServer{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster",
},
Status: operatorv1.KubeAPIServerStatus{
StaticPodOperatorStatus: operatorv1.StaticPodOperatorStatus{
NodeStatuses: tt.nodeStatuses,
},
},
})

authIndexer := cache.NewIndexer(func(obj interface{}) (string, error) {
return "cluster", nil
}, cache.Indexers{})

authIndexer.Add(&configv1.Authentication{
Spec: configv1.AuthenticationSpec{
Type: tt.authType,
},
})

available, err := ExternalOIDCConfigAvailable(
configv1listers.NewAuthenticationLister(authIndexer),
operatorv1listers.NewKubeAPIServerLister(kasIndexer),
corelistersv1.NewConfigMapLister(cmIndexer),
)

if tt.expectError != (err != nil) {
t.Fatalf("expected error %v; got %v", tt.expectError, err)
}

if tt.expectAvailable != available {
t.Fatalf("expected available %v; got %v", tt.expectAvailable, available)
}
})
}
}

func cm(name, dataKey, dataValue string) *corev1.ConfigMap {
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "openshift-kube-apiserver",
},
}

if len(dataKey) > 0 {
cm.Data = map[string]string{
dataKey: dataValue,
}
}

return cm
}
Loading