Skip to content

Commit

Permalink
Require Admin to restart Oauth2Proxy (#674)
Browse files Browse the repository at this point in the history
* Require Admin to restart Oauth2Proxy

* go mod tidy

* Tests

* fix suggestions

* fix test

* fix test
  • Loading branch information
Richard87 authored Sep 19, 2024
1 parent 0732e20 commit cd1c04e
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 20 deletions.
1 change: 1 addition & 0 deletions api/applications/applications_handler_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package applications

import (
"context"

"github.com/equinor/radix-api/api/utils/access"
"github.com/equinor/radix-api/models"
authorizationapi "k8s.io/api/authorization/v1"
Expand Down
7 changes: 6 additions & 1 deletion api/environments/component_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN
// RestartComponentAuxiliaryResource Restarts a component's auxiliary resource
func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Context, appName, envName, componentName, auxType string) error {
log.Ctx(ctx).Info().Msgf("Restarting auxiliary resource %s for component %s, %s", auxType, componentName, appName)
if isAdmin, err := kubequery.IsRadixApplicationAdmin(ctx, eh.accounts.UserAccount.Client, appName); err != nil {
return err
} else if !isAdmin {
return http.ForbiddenError("you must be administrator to restart the Oauth2 Proxy service")
}

radixDeployment, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName)
if err != nil {
Expand Down Expand Up @@ -163,7 +168,7 @@ func canDeploymentBeRestarted(deployment *appsv1.Deployment) bool {
}

func (eh EnvironmentHandler) patchDeploymentForRestart(ctx context.Context, deployment *appsv1.Deployment) error {
deployClient := eh.accounts.UserAccount.Client.AppsV1().Deployments(deployment.GetNamespace())
deployClient := eh.accounts.ServiceAccount.Client.AppsV1().Deployments(deployment.GetNamespace())

return retry.RetryOnConflict(retry.DefaultRetry, func() error {
deployToPatch, err := deployClient.Get(ctx, deployment.GetName(), metav1.GetOptions{})
Expand Down
48 changes: 44 additions & 4 deletions api/environments/environment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
operatorutils "github.com/equinor/radix-operator/pkg/apis/utils"
"github.com/equinor/radix-operator/pkg/apis/utils/labels"
radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned"
"github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake"
radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake"
"github.com/golang/mock/gomock"
kedav2 "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned"
kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake"
Expand All @@ -43,10 +43,13 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
authorizationapiv1 "k8s.io/api/authorization/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
kubefake "k8s.io/client-go/kubernetes/fake"
testing2 "k8s.io/client-go/testing"
secretsstorevclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned"
secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake"
)
Expand All @@ -64,10 +67,10 @@ const (
subscriptionId = "12347718-c8f8-4995-bfbb-02655ff1f89c"
)

func setupTest(t *testing.T, envHandlerOpts []EnvironmentHandlerOptions) (*commontest.Utils, *controllertest.Utils, *controllertest.Utils, kubernetes.Interface, radixclient.Interface, kedav2.Interface, prometheusclient.Interface, secretsstorevclient.Interface, *certclientfake.Clientset) {
func setupTest(t *testing.T, envHandlerOpts []EnvironmentHandlerOptions) (*commontest.Utils, *controllertest.Utils, *controllertest.Utils, *kubefake.Clientset, radixclient.Interface, kedav2.Interface, prometheusclient.Interface, secretsstorevclient.Interface, *certclientfake.Clientset) {
// Setup
kubeclient := kubefake.NewSimpleClientset()
radixClient := fake.NewSimpleClientset()
kubeclient := kubefake.NewClientset()
radixClient := radixfake.NewSimpleClientset()
kedaClient := kedafake.NewSimpleClientset()
prometheusclient := prometheusfake.NewSimpleClientset()
secretproviderclient := secretproviderfake.NewSimpleClientset()
Expand Down Expand Up @@ -1005,13 +1008,42 @@ func Test_GetEnvironmentEvents_Handler(t *testing.T) {

func TestRestartAuxiliaryResource(t *testing.T) {
auxType := "oauth"
called := 0

// Setup
commonTestUtils, environmentControllerTestUtils, _, kubeClient, _, _, _, _, _ := setupTest(t, nil)
kubeClient.Fake.PrependReactor("create", "*", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
createAction, ok := action.DeepCopy().(testing2.CreateAction)
if !ok {
return false, nil, nil
}

review, ok := createAction.GetObject().(*authorizationapiv1.SelfSubjectAccessReview)
if !ok {
return false, nil, nil
}

called++

if review.Spec.ResourceAttributes.Name != anyAppName {
return true, review, nil
}

assert.Equal(t, review.Spec.ResourceAttributes.Name, anyAppName)
assert.Equal(t, review.Spec.ResourceAttributes.Resource, v1.ResourceRadixRegistrations)
assert.Equal(t, review.Spec.ResourceAttributes.Verb, "patch")

review.Status.Allowed = true
return true, review, nil
})
_, err := commonTestUtils.ApplyRegistration(operatorutils.
NewRegistrationBuilder().
WithName(anyAppName))
require.NoError(t, err)
_, err = commonTestUtils.ApplyRegistration(operatorutils.
NewRegistrationBuilder().
WithName("forbidden"))
require.NoError(t, err)
_, err = commonTestUtils.ApplyApplication(operatorutils.
NewRadixApplicationBuilder().
WithAppName(anyAppName).
Expand Down Expand Up @@ -1045,9 +1077,17 @@ func TestRestartAuxiliaryResource(t *testing.T) {
responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/aux/%s/restart", anyAppName, anyEnvironment, anyComponentName, auxType))
response := <-responseChannel
assert.Equal(t, http.StatusOK, response.Code)
assert.Equal(t, 1, called)

kubeDeploy, _ := kubeClient.AppsV1().Deployments(envNs).Get(context.Background(), "comp1-aux-resource", metav1.GetOptions{})
assert.NotEmpty(t, kubeDeploy.Spec.Template.Annotations[restartedAtAnnotation])

// Test Forbidden for other app names

responseChannel = environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/aux/%s/restart", "forbidden", anyEnvironment, anyComponentName, auxType))
response = <-responseChannel
assert.Equal(t, http.StatusForbidden, response.Code)
assert.Equal(t, 2, called)
}

func Test_GetJobs(t *testing.T) {
Expand Down
18 changes: 9 additions & 9 deletions api/environments/environment_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/equinor/radix-common/utils/slice"
deployUtils "github.com/equinor/radix-operator/pkg/apis/deployment"
"github.com/equinor/radix-operator/pkg/apis/kube"
v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
k8sObjectUtils "github.com/equinor/radix-operator/pkg/apis/utils"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -126,7 +126,7 @@ func (eh EnvironmentHandler) GetEnvironmentSummary(ctx context.Context, appName
if err != nil {
return nil, err
}
envNames := slice.Map(reList, func(re v1.RadixEnvironment) string { return re.Spec.EnvName })
envNames := slice.Map(reList, func(re radixv1.RadixEnvironment) string { return re.Spec.EnvName })
rdList, err := kubequery.GetRadixDeploymentsForEnvironments(ctx, eh.accounts.UserAccount.RadixClient, appName, envNames, 10)
if err != nil {
return nil, err
Expand Down Expand Up @@ -208,7 +208,7 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam
}

// CreateEnvironment Handler for CreateEnvironment. Creates an environment if it does not exist
func (eh EnvironmentHandler) CreateEnvironment(ctx context.Context, appName, envName string) (*v1.RadixEnvironment, error) {
func (eh EnvironmentHandler) CreateEnvironment(ctx context.Context, appName, envName string) (*radixv1.RadixEnvironment, error) {
// ensure application exists
rr, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{})
if err != nil {
Expand Down Expand Up @@ -286,7 +286,7 @@ func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName
}
return slice.Map(
slice.FindAll(reList, predicate.IsNotOrphanEnvironment),
func(re v1.RadixEnvironment) string { return re.Spec.EnvName },
func(re radixv1.RadixEnvironment) string { return re.Spec.EnvName },
), nil
}

Expand Down Expand Up @@ -315,7 +315,7 @@ func (eh EnvironmentHandler) StopEnvironment(ctx context.Context, appName, envNa
return err
}
if radixDeployment == nil {
return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found")
return http.ValidationError(radixv1.KindRadixDeployment, "no radix deployments found")
}

log.Ctx(ctx).Info().Msgf("Stopping components in environment %s, %s", envName, appName)
Expand All @@ -335,7 +335,7 @@ func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx con
return err
}
if radixDeployment == nil {
return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found")
return http.ValidationError(radixv1.KindRadixDeployment, "no radix deployments found")
}

log.Ctx(ctx).Info().Msgf("Starting components in environment %s, %s", envName, appName)
Expand All @@ -356,7 +356,7 @@ func (eh EnvironmentHandler) RestartEnvironment(ctx context.Context, appName, en
return err
}
if radixDeployment == nil {
return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found")
return http.ValidationError(radixv1.KindRadixDeployment, "no radix deployments found")
}

log.Ctx(ctx).Info().Msgf("Restarting components in environment %s, %s", envName, appName)
Expand Down Expand Up @@ -423,7 +423,7 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context,
return nil, err
}
if rd == nil {
return nil, http.ValidationError(v1.KindRadixDeployment, "no radix deployments found")
return nil, http.ValidationError(radixv1.KindRadixDeployment, "no radix deployments found")
}
baseUpdater := &baseComponentUpdater{
appName: appName,
Expand All @@ -432,7 +432,7 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context,
radixDeployment: rd,
}
var updater radixDeployCommonComponentUpdater
var componentToPatch v1.RadixCommonDeployComponent
var componentToPatch radixv1.RadixCommonDeployComponent
componentIndex, componentToPatch := deployUtils.GetDeploymentComponent(rd, componentName)
if !radixutils.IsNil(componentToPatch) {
updater = &radixDeployComponentUpdater{base: baseUpdater}
Expand Down
13 changes: 13 additions & 0 deletions api/kubequery/radixapplication.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ package kubequery
import (
"context"

"github.com/equinor/radix-api/api/utils/access"
radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils"
radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned"
authorizationapi "k8s.io/api/authorization/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

// GetRadixApplication returns the RadixApplication for the specified application name.
func GetRadixApplication(ctx context.Context, client radixclient.Interface, appName string) (*radixv1.RadixApplication, error) {
ns := operatorUtils.GetAppNamespace(appName)
return client.RadixV1().RadixApplications(ns).Get(ctx, appName, metav1.GetOptions{})
}

func IsRadixApplicationAdmin(ctx context.Context, kubeClient kubernetes.Interface, appName string) (bool, error) {
return access.HasAccess(ctx, kubeClient, &authorizationapi.ResourceAttributes{
Verb: "patch",
Group: radixv1.GroupName,
Resource: radixv1.ResourceRadixRegistrations,
Version: "*",
Name: appName,
})
}
35 changes: 35 additions & 0 deletions api/kubequery/radixapplication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import (
radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
authorizationapiv1 "k8s.io/api/authorization/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
testing2 "k8s.io/client-go/testing"
)

func Test_GetRadixApplication(t *testing.T) {
Expand All @@ -26,3 +30,34 @@ func Test_GetRadixApplication(t *testing.T) {
_, err = GetRadixApplication(context.Background(), client, "app2")
assert.True(t, errors.IsNotFound(err))
}

func Test_IsRadixApplicationAdmin(t *testing.T) {
called := 0
client := fake.NewClientset()
client.Fake.PrependReactor("create", "*", func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
createAction, ok := action.DeepCopy().(testing2.CreateAction)
if !ok {
return false, nil, nil
}

review, ok := createAction.GetObject().(*authorizationapiv1.SelfSubjectAccessReview)
if !ok {
return false, nil, nil
}

called++
assert.Equal(t, review.Spec.ResourceAttributes, &authorizationapiv1.ResourceAttributes{
Verb: "patch",
Group: radixv1.GroupName,
Resource: radixv1.ResourceRadixRegistrations,
Version: "*",
Name: "any-app-name",
})
return true, review, nil
})

actual, err := IsRadixApplicationAdmin(context.Background(), client, "any-app-name")
require.NoError(t, err)
assert.False(t, actual, "Should be false since we dont set it to true in our reactor")
assert.Equal(t, 1, called)
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ toolchain go1.22.5

require (
github.com/cert-manager/cert-manager v1.15.0
github.com/equinor/radix-common v1.9.4
github.com/equinor/radix-common v1.9.5
github.com/equinor/radix-job-scheduler v1.11.0
github.com/equinor/radix-operator v1.58.3
github.com/equinor/radix-operator v1.59.1
github.com/evanphx/json-patch/v5 v5.9.0
github.com/felixge/httpsnoop v1.0.4
github.com/golang-jwt/jwt/v5 v5.2.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/equinor/radix-common v1.9.4 h1:ErSnB2tqlRwaQuQdaA0qzsReDtHDgubcvqRO098ncEw=
github.com/equinor/radix-common v1.9.4/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY=
github.com/equinor/radix-common v1.9.5 h1:p1xldkYUoavwIMguoxxOyVkOXLPA6K8qMsgzeztQtQw=
github.com/equinor/radix-common v1.9.5/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY=
github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY=
github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc=
github.com/equinor/radix-operator v1.58.3 h1:F4YhNkQ4uRONP125OTfG8hdy9PiyKlOWVO8/p2NIi70=
github.com/equinor/radix-operator v1.58.3/go.mod h1:DTPXOxU3uHPvji7qBGSK1b03iXROpX3l94kYjcOHkPM=
github.com/equinor/radix-operator v1.59.1 h1:wzb2tF4MWtGDWmyYuIv3oh17G5Bx8ztW9O+WgnI3QFc=
github.com/equinor/radix-operator v1.59.1/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8=
github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=
github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
Expand Down

0 comments on commit cd1c04e

Please sign in to comment.