Skip to content

Commit

Permalink
feat(service-proxy): add optionValues to configure oauth2-proxy (#885)
Browse files Browse the repository at this point in the history
* feat(service-proxy): add optionValues to configure oauth2-proxy

* hide oauth-proxy behind annotation

---------

Co-authored-by: Abhijith Ravindra <137736216+abhijith-darshan@users.noreply.github.com>
  • Loading branch information
IvoGoman and abhijith-darshan authored Feb 17, 2025
1 parent c2e06bb commit 5ebeedf
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 2 deletions.
91 changes: 90 additions & 1 deletion pkg/controllers/organization/service_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package organization

import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"

Expand All @@ -24,7 +26,11 @@ import (
"github.com/cloudoperators/greenhouse/pkg/version"
)

const serviceProxyName = "service-proxy"
const (
serviceProxyName = "service-proxy"
cookieSecretKey = "oauth2proxy-cookie-secret" //nolint:gosec
oauthPreviewAnnotation = "greenhouse.sap/oauth-proxy-preview"
)

func (r *OrganizationReconciler) reconcileServiceProxy(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
domain := fmt.Sprintf("%s.%s", org.Name, common.DNSDomain)
Expand All @@ -47,6 +53,40 @@ func (r *OrganizationReconciler) reconcileServiceProxy(ctx context.Context, org
return nil
}

// TODO: remove this once the feature is considered stable.
// This allows to enable/disable the oauth-proxy feature for a specific organization
oauthProxyEnabled := false
if val, ok := org.GetAnnotations()[oauthPreviewAnnotation]; ok {
oauthProxyEnabled = val == "true"
}

if oauthProxyEnabled {
// oauth2-proxy requires OIDC Client config
if org.Spec.Authentication == nil || org.Spec.Authentication.OIDCConfig == nil {
log.FromContext(ctx).Info("skipping service-proxy Plugin reconciliation, Organization has no OIDCConfig")
return nil
}

// oauth2-proxy requires a cookie secret, which needs to be provided from a secret
secret := &corev1.Secret{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: org.Spec.Authentication.OIDCConfig.ClientSecretReference.Name, Namespace: org.Name}, secret); err != nil {
log.FromContext(ctx).Info("failed to get Organization OIDC Secret", "error", err)
return nil
}
if _, ok := secret.Data[cookieSecretKey]; !ok {
cookieData, err := generateCookieSecret()
if err != nil {
log.FromContext(ctx).Info("failed to generate cookie secret", "error", err)
return err
}
secret.Data[cookieSecretKey] = []byte(cookieData)
if err := r.Client.Update(ctx, secret); err != nil {
log.FromContext(ctx).Info("failed to update Organization OIDC Secret with cookie secret", "error", err)
return fmt.Errorf("failed to update Organization OIDC Secret with cookie secret: %w", err)
}
}
}

plugin := &greenhousesapv1alpha1.Plugin{
ObjectMeta: metav1.ObjectMeta{
Name: serviceProxyName,
Expand All @@ -69,6 +109,43 @@ func (r *OrganizationReconciler) reconcileServiceProxy(ctx context.Context, org
Value: &apiextensionsv1.JSON{Raw: versionJSON},
},
}
if oauthProxyEnabled {
oauthProxyValues := []greenhousesapv1alpha1.PluginOptionValue{
{
Name: "oauth2proxy.enabled",
Value: &apiextensionsv1.JSON{Raw: []byte("\"true\"")},
},
{
Name: "oauth2proxy.issuerURL",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.Issuer))},
},
{
Name: "oauth2proxy.clientIDRef.secret",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.ClientIDReference.Name))},
},
{
Name: "oauth2proxy.clientIDRef.key",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.ClientIDReference.Key))},
},
{
Name: "oauth2proxy.clientSecretRef.secret",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.ClientSecretReference.Name))},
},
{
Name: "oauth2proxy.clientSecretRef.key",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.ClientSecretReference.Key))},
},
{
Name: "oauth2proxy.cookieSecretRef.secret",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", org.Spec.Authentication.OIDCConfig.ClientSecretReference.Name))},
},
{
Name: "oauth2proxy.cookieSecretRef.key",
Value: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf("\"%s\"", cookieSecretKey))},
},
}
plugin.Spec.OptionValues = append(plugin.Spec.OptionValues, oauthProxyValues...)
}
return controllerutil.SetControllerReference(org, plugin, r.Scheme())
})
if err != nil {
Expand Down Expand Up @@ -100,3 +177,15 @@ func listOrganizationsAsReconcileRequests(ctx context.Context, c client.Client,
}
return res
}

// generateCookieSecret generates a random cookie secret
func generateCookieSecret() (string, error) {
// Generate 16 random bytes
token := make([]byte, 16)
if _, err := rand.Read(token); err != nil {
return "", err
}
// Base64 encode the token twice
encodedToken := base64.StdEncoding.EncodeToString(token)
return base64.StdEncoding.EncodeToString([]byte(encodedToken)), nil
}
33 changes: 32 additions & 1 deletion pkg/controllers/organization/service_proxy_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors
// SPDX-License-Identifier: Apache-2.0

package organization_test
package organization

import (
. "github.com/onsi/ginkgo/v2"
Expand All @@ -11,6 +11,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"

greenhousev1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1"
"github.com/cloudoperators/greenhouse/pkg/test"
)
Expand Down Expand Up @@ -96,4 +98,33 @@ var _ = Describe("Organization ServiceProxyReconciler", Ordered, func() {
}).Should(Succeed(), "service-proxy plugin should have been created for organization")
})
})

When("organization is annotated with the oauth2proxy preview annotation", func() {
It("should enable the oauth-proxy feature for the organization", func() {
By("creating a secret with dummy oidc config")
secret := setup.CreateSecret(test.Ctx, "oidc-config", test.WithSecretData(map[string][]byte{"clientID": []byte("dummy"), "clientSecret": []byte("top-secret")}))

By("annotating the organization")
addAnnotation := func(org *greenhousev1alpha1.Organization) {
annotations := org.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
annotations[oauthPreviewAnnotation] = "true"
org.SetAnnotations(annotations)
}

By("creating an organization with the oauthpreview annotation & oauth config")
org := setup.CreateOrganization(test.Ctx, setup.Namespace(), addAnnotation, test.WithOIDCConfig("some-issuer.tld", secret.Name, "clientID", "clientSecret"))

By("ensuring a service-proxy plugin has been created for organization")
Eventually(func(g Gomega) {
var plugin = new(greenhousev1alpha1.Plugin)
err := test.K8sClient.Get(test.Ctx, types.NamespacedName{Name: "service-proxy", Namespace: org.Name}, plugin)
g.Expect(err).ToNot(HaveOccurred(), "service-proxy plugin should have been created by controller")
g.Expect(plugin.Spec.OptionValues).To(ContainElement(greenhousev1alpha1.PluginOptionValue{Name: "oauth2proxy.enabled", Value: &apiextensionsv1.JSON{Raw: []byte("\"true\"")}}))
}).Should(Succeed(), "service-proxy plugin should have been created for organization")

})
})
})

0 comments on commit 5ebeedf

Please sign in to comment.