Skip to content

Commit

Permalink
Added handling cluster OIDC flags
Browse files Browse the repository at this point in the history
  • Loading branch information
vvondruska committed Jul 22, 2024
1 parent 50491a1 commit 1f95113
Show file tree
Hide file tree
Showing 30 changed files with 1,288 additions and 349 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Dockerfile.cross
# Ignore DS_Store files
.DS_Store
**/.DS_Store
dex-operator
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added handling of the new `cluster.giantswarm.io/update-oidc-flags` annotation in the Dex app - if set to `true`, it will add OIDC flags to the cluster configuration and force node roll, unless the OIDC flags already exist.

## [0.12.1] - 2023-12-06

### Changed
Expand Down
25 changes: 25 additions & 0 deletions controllers/app_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/giantswarm/dex-operator/pkg/auth"
"github.com/giantswarm/dex-operator/pkg/clusteroidc"
"github.com/giantswarm/dex-operator/pkg/idp"
"github.com/giantswarm/dex-operator/pkg/idp/provider"
"github.com/giantswarm/dex-operator/pkg/idp/provider/azure"
Expand Down Expand Up @@ -131,6 +132,24 @@ func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
}
}

var oidcService *clusteroidc.Service
{
var err error
c := clusteroidc.Config{
Log: log,
Client: r.Client,
App: app,
ManagementClusterBaseDomain: r.BaseDomain,
ManagementClusterIssuerAddress: r.IssuerAddress,
ManagementClusterName: r.ManagementCluster,
}

oidcService, err = clusteroidc.New(c)
if err != nil {
return ctrl.Result{}, microerror.Mask(err)
}
}

// App is deleted.
if !app.DeletionTimestamp.IsZero() {
if err := idpService.ReconcileDelete(ctx); err != nil {
Expand All @@ -139,6 +158,9 @@ func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
if err := authService.ReconcileDelete(ctx); err != nil {
return ctrl.Result{}, microerror.Mask(err)
}
if err := oidcService.ReconcileDelete(ctx); err != nil {
return ctrl.Result{}, microerror.Mask(err)
}
// remove finalizer
if controllerutil.ContainsFinalizer(app, key.DexOperatorFinalizer) {
controllerutil.RemoveFinalizer(app, key.DexOperatorFinalizer)
Expand All @@ -165,6 +187,9 @@ func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
if err := idpService.Reconcile(ctx); err != nil {
return ctrl.Result{}, microerror.Mask(err)
}
if err := oidcService.Reconcile(ctx); err != nil {
return ctrl.Result{}, microerror.Mask(err)
}
return DefaultRequeue(), nil
}

Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
github.com/cloudflare/circl v1.3.6 // indirect
github.com/coredns/caddy v1.1.0 // indirect
github.com/coredns/corefile-migration v1.0.20 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
Expand Down Expand Up @@ -88,6 +91,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
Expand Down Expand Up @@ -121,6 +125,7 @@ require (
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.27.4 // indirect
k8s.io/cluster-bootstrap v0.27.2 // indirect
k8s.io/component-base v0.27.4 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
Expand Down
143 changes: 143 additions & 0 deletions go.sum

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions pkg/app/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package app

import (
"context"

"github.com/giantswarm/dex-operator/pkg/key"

"github.com/giantswarm/apiextensions-application/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type Config struct {
RedirectURI string
Name string
IssuerURI string
IdentifierURI string
SecretValidityMonths int
}

type ManagementClusterProps struct {
Name string
BaseDomain string
IssuerAddress string
}

func GetConfig(ctx context.Context, app *v1alpha1.App, client client.Client, managementCluster ManagementClusterProps) (Config, error) {
var baseDomain string

// Get the cluster values configmap if present (workload cluster format)
if ClusterValuesIsPresent(app) {
clusterValuesConfigmap := &corev1.ConfigMap{}
if err := client.Get(ctx, types.NamespacedName{
Name: app.Spec.Config.ConfigMap.Name,
Namespace: app.Spec.Config.ConfigMap.Namespace},
clusterValuesConfigmap); err != nil {
return Config{}, err
}
// Get the base domain
baseDomain = GetBaseDomainFromClusterValues(clusterValuesConfigmap)
}
issuerAddress := GetIssuerAddress(baseDomain, managementCluster.IssuerAddress, managementCluster.BaseDomain)

return Config{
Name: key.GetIdpAppName(managementCluster.Name, app.Namespace, app.Name),
IssuerURI: key.GetIssuerURI(issuerAddress),
RedirectURI: key.GetRedirectURI(issuerAddress),
IdentifierURI: key.GetIdentifierURI(key.GetIdpAppName(managementCluster.Name, app.Namespace, app.Name)),
SecretValidityMonths: key.SecretValidityMonths,
}, nil
}
108 changes: 108 additions & 0 deletions pkg/app/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package app

import (
"context"
"fmt"
"reflect"
"testing"

"github.com/giantswarm/dex-operator/pkg/key"
"github.com/giantswarm/dex-operator/pkg/tests"

"github.com/giantswarm/apiextensions-application/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestGetAppConfig(t *testing.T) {
testCases := []struct {
name string
managementClusterName string
managementClusterBaseDomain string
managementClusterIssuerAddress string
app *v1alpha1.App
clusterValuesConfigMap *corev1.ConfigMap
expectedAppConfig Config
}{
{
name: "case 0: Get issuer URL from cluster config values",
managementClusterName: "testcluster",
managementClusterBaseDomain: "base.domain.io",
managementClusterIssuerAddress: "issuer.cluster.base.domain.io",
app: tests.GetExampleApp(),
clusterValuesConfigMap: getClusterValuesConfigMap("baseDomain: wc.cluster.domain.io"),
expectedAppConfig: Config{
Name: "testcluster-example-test",
IssuerURI: "https://dex.wc.cluster.domain.io",
RedirectURI: "https://dex.wc.cluster.domain.io/callback",
IdentifierURI: "https://dex.giantswarm.io/testcluster-example-test",
SecretValidityMonths: key.SecretValidityMonths,
},
},
{
name: "case 1: Get issuer URL from management cluster issuer URL property",
managementClusterName: "testcluster",
managementClusterBaseDomain: "base.domain.io",
managementClusterIssuerAddress: "issuer.cluster.domain.io",
app: tests.GetExampleApp(),
expectedAppConfig: Config{
Name: "testcluster-example-test",
IssuerURI: "https://issuer.cluster.domain.io",
RedirectURI: "https://issuer.cluster.domain.io/callback",
IdentifierURI: "https://dex.giantswarm.io/testcluster-example-test",
SecretValidityMonths: key.SecretValidityMonths,
},
},
{
name: "case 2: Get issuer URL from management cluster base domain",
managementClusterName: "testcluster",
managementClusterBaseDomain: "base.domain.io",
app: tests.GetExampleApp(),
expectedAppConfig: Config{
Name: "testcluster-example-test",
IssuerURI: "https://dex.g8s.base.domain.io",
RedirectURI: "https://dex.g8s.base.domain.io/callback",
IdentifierURI: "https://dex.giantswarm.io/testcluster-example-test",
SecretValidityMonths: key.SecretValidityMonths,
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {

ctx := context.Background()

fakeClientBuilder := fake.NewClientBuilder()
if tc.clusterValuesConfigMap != nil {
tc.app.Spec = v1alpha1.AppSpec{
Config: v1alpha1.AppSpecConfig{
ConfigMap: v1alpha1.AppSpecConfigConfigMap{
Name: tc.clusterValuesConfigMap.Name,
Namespace: tc.clusterValuesConfigMap.Namespace,
},
},
}
fakeClientBuilder.WithObjects(tc.clusterValuesConfigMap)
}

appConfig, err := GetConfig(ctx, tc.app, fakeClientBuilder.Build(), ManagementClusterProps{
Name: tc.managementClusterName,
BaseDomain: tc.managementClusterBaseDomain,
IssuerAddress: tc.managementClusterIssuerAddress,
})
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(appConfig, tc.expectedAppConfig) {
t.Fatalf("Expacted %v, got %v", tc.expectedAppConfig, appConfig)
}
})
}
}

func getClusterValuesConfigMap(clusterValues string) *corev1.ConfigMap {
name := fmt.Sprintf("test-%s", key.ClusterValuesConfigmapSuffix)
return tests.GetClusterValuesConfigMap(name, "example", clusterValues)
}
61 changes: 61 additions & 0 deletions pkg/app/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package app

import (
"fmt"
"reflect"
"regexp"
"strings"

"github.com/giantswarm/dex-operator/pkg/key"

"github.com/giantswarm/apiextensions-application/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
)

func RemoveExtraConfig(extraConfigs []v1alpha1.AppExtraConfig, extraConfig v1alpha1.AppExtraConfig) []v1alpha1.AppExtraConfig {
if extraConfigs == nil {
return extraConfigs
}
result := []v1alpha1.AppExtraConfig{}
for _, config := range extraConfigs {
if !reflect.DeepEqual(config, extraConfig) {
result = append(result, config)
}
}
return result
}

func ClusterValuesIsPresent(app *v1alpha1.App) bool {
return strings.HasSuffix(app.Spec.Config.ConfigMap.Name, key.ClusterValuesConfigmapSuffix)
}

func GetIssuerAddress(baseDomain string, managementClusterIssuerAddress string, managementClusterBaseDomain string) string {
var issuerAddress string
{
// Derive issuer address from cluster basedomain if it exists
if baseDomain != "" {
issuerAddress = key.GetIssuerAddress(baseDomain)
}

// Otherwise fall back to management cluster issuer address if present
if issuerAddress == "" {
issuerAddress = managementClusterIssuerAddress
}

// If all else fails, fall back to the base domain (only works in vintage)
if issuerAddress == "" {
clusterDomain := key.GetVintageClusterDomain(managementClusterBaseDomain)
issuerAddress = key.GetIssuerAddress(clusterDomain)
}
}
return issuerAddress
}

func GetBaseDomainFromClusterValues(clusterValuesConfigmap *corev1.ConfigMap) string {
values := clusterValuesConfigmap.Data[key.ValuesConfigMapKey]
rex := regexp.MustCompile(fmt.Sprintf(`(%v)(\s*:\s*)(\S+)`, key.BaseDomainKey))
if matches := rex.FindStringSubmatch(values); len(matches) > 3 {
return matches[3]
}
return ""
}
Loading

0 comments on commit 1f95113

Please sign in to comment.