Skip to content

Commit

Permalink
implement RBAC check in AdmissionController (#25)
Browse files Browse the repository at this point in the history
1. Implement RBAC check in AdmissionController
2. Write unit tests for AdmissionController
3. Enabled unit tests in CI
  • Loading branch information
dgrove-oss authored Feb 23, 2024
1 parent 13bae6b commit 632b43d
Show file tree
Hide file tree
Showing 16 changed files with 521 additions and 207 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
run: |
make docker-build -e GIT_BRANCH=${{ env.GIT_BRANCH }} TAG=${{ env.GIT_BRANCH }}-${{ env.TAG }}
- name: Run Unit Tests
run: make test

- name: Create and configure cluster
run: ./hack/create-test-cluster.sh

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ vet: ## Run go vet against code.

.PHONY: test
test: manifests generate fmt vet envtest ## Run unit tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -v -ginkgo.v -coverprofile cover.out

# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
Expand Down
11 changes: 5 additions & 6 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func main() {
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&config.ManageJobsWithoutQueueName, "manage-no-queue", true, "Manage AppWrappers without queue names")
flag.StringVar(&config.ServiceAccountName, "service-account", "", "Service account name for controller")
opts := zap.Options{
Development: true,
}
Expand All @@ -83,6 +84,7 @@ func main() {

ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
setupLog.Info("Build info", "version", BuildVersion, "date", BuildDate)
setupLog.Info("Configuration", "config", config)

// if the enable-http2 flag is false (the default), http/2 should be disabled
// due to its vulnerabilities. More specifically, disabling http/2 will
Expand Down Expand Up @@ -150,12 +152,9 @@ func main() {
os.Exit(1)
}
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
wh := &controller.AppWrapperWebhook{Config: &config}
if err := ctrl.NewWebhookManagedBy(mgr).
For(&workloadv1beta2.AppWrapper{}).
WithDefaulter(wh).
WithValidator(wh).
Complete(); err != nil {
if err = (&controller.AppWrapperWebhook{
Config: &config,
}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "AppWrapper")
os.Exit(1)
}
Expand Down
1 change: 1 addition & 0 deletions config/default/manager_auth_proxy_patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ spec:
memory: 64Mi
- name: manager
args:
- "--service-account=system:serviceaccount:$(MY_NAMESPACE):$(MY_SERVICE_ACCOUNT_NAME)"
- "--health-probe-bind-address=:8081"
- "--metrics-bind-address=127.0.0.1:8080"
- "--leader-elect"
12 changes: 10 additions & 2 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ spec:
- /manager
args:
- --leader-elect
- "--service-account=system:serviceaccount:$(MY_NAMESPACE):$(MY_SERVICE_ACCOUNT_NAME)"
image: controller:latest
name: manager
securityContext:
Expand All @@ -89,14 +90,21 @@ spec:
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
# TODO(user): Configure the resources accordingly based on the project requirements.
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 10m
memory: 64Mi
env:
- name: MY_SERVICE_ACCOUNT_NAME
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: MY_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
serviceAccountName: controller-manager
terminationGracePeriodSeconds: 10
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ rules:
- patch
- update
- watch
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- list
- apiGroups:
- apps
resources:
Expand All @@ -39,6 +45,12 @@ rules:
- patch
- update
- watch
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
- apiGroups:
- batch
resources:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
k8s.io/client-go v0.29.1
sigs.k8s.io/controller-runtime v0.17.0
sigs.k8s.io/kueue v0.6.0
sigs.k8s.io/yaml v1.4.0
)

require (
Expand Down Expand Up @@ -73,5 +74,4 @@ require (
sigs.k8s.io/jobset v0.3.1 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ limitations under the License.
package controller

type AppWrapperConfig struct {
ManageJobsWithoutQueueName bool `json:"manageJobsWithoutQueueName,omitempty"`
ManageJobsWithoutQueueName bool `json:"manageJobsWithoutQueueName,omitempty"`
ServiceAccountName string `json:"serviceAccountName,omitempty"`
}
4 changes: 4 additions & 0 deletions internal/controller/appwrapper_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.

package controller

/*
import (
"context"
Expand Down Expand Up @@ -82,3 +84,5 @@ var _ = Describe("AppWrapper Controller", func() {
})
})
})
*/
173 changes: 173 additions & 0 deletions internal/controller/appwrapper_fixtures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
Copyright 2024 IBM Corporation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
"fmt"
"math/rand"
"time"

. "github.com/onsi/gomega"

workloadv1beta2 "github.com/project-codeflare/appwrapper/api/v1beta2"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"
)

const charset = "abcdefghijklmnopqrstuvwxyz0123456789"

func randName(baseName string) string {
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, 6)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return fmt.Sprintf("%s-%s", baseName, string(b))
}

func toAppWrapper(components ...workloadv1beta2.AppWrapperComponent) *workloadv1beta2.AppWrapper {
return &workloadv1beta2.AppWrapper{
ObjectMeta: metav1.ObjectMeta{Name: randName("aw"), Namespace: "default"},
Spec: workloadv1beta2.AppWrapperSpec{Components: components},
}
}

const podYAML = `
apiVersion: v1
kind: Pod
metadata:
name: %v
spec:
restartPolicy: Never
containers:
- name: busybox
image: quay.io/project-codeflare/busybox:1.36
command: ["sh", "-c", "sleep 1000"]
resources:
requests:
cpu: %v`

func pod(milliCPU int64) workloadv1beta2.AppWrapperComponent {
yamlString := fmt.Sprintf(podYAML,
randName("pod"),
resource.NewMilliQuantity(milliCPU, resource.DecimalSI))

jsonBytes, err := yaml.YAMLToJSON([]byte(yamlString))
Expect(err).NotTo(HaveOccurred())
replicas := int32(1)
return workloadv1beta2.AppWrapperComponent{
PodSets: []workloadv1beta2.AppWrapperPodSet{{Replicas: &replicas, Path: "template"}},
Template: runtime.RawExtension{Raw: jsonBytes},
}
}

const namespacedPodYAML = `
apiVersion: v1
kind: Pod
metadata:
name: %v
namespace: %v
spec:
restartPolicy: Never
containers:
- name: busybox
image: quay.io/project-codeflare/busybox:1.36
command: ["sh", "-c", "sleep 1000"]
resources:
requests:
cpu: %v`

func namespacedPod(namespace string, milliCPU int64) workloadv1beta2.AppWrapperComponent {
yamlString := fmt.Sprintf(namespacedPodYAML,
randName("pod"),
namespace,
resource.NewMilliQuantity(milliCPU, resource.DecimalSI))

jsonBytes, err := yaml.YAMLToJSON([]byte(yamlString))
Expect(err).NotTo(HaveOccurred())
replicas := int32(1)
return workloadv1beta2.AppWrapperComponent{
PodSets: []workloadv1beta2.AppWrapperPodSet{{Replicas: &replicas, Path: "template"}},
Template: runtime.RawExtension{Raw: jsonBytes},
}
}

const serviceYAML = `
apiVersion: v1
kind: Service
metadata:
name: %v
spec:
selector:
app: test
ports:
- protocol: TCP
port: 80
targetPort: 8080`

func service() workloadv1beta2.AppWrapperComponent {
yamlString := fmt.Sprintf(serviceYAML, randName("service"))
jsonBytes, err := yaml.YAMLToJSON([]byte(yamlString))
Expect(err).NotTo(HaveOccurred())
return workloadv1beta2.AppWrapperComponent{
PodSets: []workloadv1beta2.AppWrapperPodSet{},
Template: runtime.RawExtension{Raw: jsonBytes},
}
}

const deploymentYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: %v
labels:
app: test
spec:
replicas: %v
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
terminationGracePeriodSeconds: 0
containers:
- name: busybox
image: quay.io/project-codeflare/busybox:1.36
command: ["sh", "-c", "sleep 10000"]
resources:
requests:
cpu: %v`

func deployment(replicaCount int, milliCPU int64) workloadv1beta2.AppWrapperComponent {
yamlString := fmt.Sprintf(deploymentYAML,
randName("deployment"),
replicaCount,
resource.NewMilliQuantity(milliCPU, resource.DecimalSI))

jsonBytes, err := yaml.YAMLToJSON([]byte(yamlString))
Expect(err).NotTo(HaveOccurred())
replicas := int32(replicaCount)
return workloadv1beta2.AppWrapperComponent{
PodSets: []workloadv1beta2.AppWrapperPodSet{{Replicas: &replicas, Path: "template.spec.template"}},
Template: runtime.RawExtension{Raw: jsonBytes},
}
}
Loading

0 comments on commit 632b43d

Please sign in to comment.