mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-06 01:17:09 +00:00
Feat: reconcile app with scoped permissions (#3434)
* Refactor: refactor multi cluster round trippers Before adding more RoundTrippers, it would be better to expose common logic in the utility package. This commit exports `tryCancelRequest` at `utils` package, and make `secretMultiClusterRoundTripper` implement `RoundTripperWrapper` interface to allow chaining multiple round trippers. Refs #3432 Signed-off-by: Sunghoon Kang <hoon@linecorp.com> * Feat: reconcile app with scoped permissions Currently, all Application resources are reconciled by the Roles bound to the controller service account. This behavior gives us the power to manage resources across multiple namespaces. However, this behavior can be problematic in the soft-multitenancy environment. This commit adds `serviceAccountName` to ApplicationSepc to reconcile Application with the given service account for reconciling Application with scoped permissions. Refs #3432 Signed-off-by: Sunghoon Kang <hoon@linecorp.com> * Refactor: extract context setter as method https://github.com/oam-dev/kubevela/pull/3434#discussion_r825561603 Signed-off-by: Sunghoon Kang <hoon@linecorp.com> * Feat: use annotation instead of spec https://github.com/oam-dev/kubevela/issues/3432#issuecomment-1066460269 Signed-off-by: Sunghoon Kang <hoon@linecorp.com> * Refactor: unify service account setter caller https://github.com/oam-dev/kubevela/pull/3434#discussion_r825853612 Signed-off-by: Sunghoon Kang <hoon@linecorp.com> * Refactor: rename GetServiceAccountName https://github.com/oam-dev/kubevela/pull/3434#discussion_r826514565 Signed-off-by: Sunghoon Kang <hoon@linecorp.com>
This commit is contained in:
@@ -25,14 +25,15 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
oamcomm "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
@@ -72,6 +73,20 @@ var _ = Describe("Application Normal tests", func() {
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
}
|
||||
|
||||
createServiceAccount := func(ns, name string) {
|
||||
sa := corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: ns,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Create(ctx, &sa)
|
||||
},
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
}
|
||||
|
||||
applyApp := func(source string) {
|
||||
By("Apply an application")
|
||||
var newApp v1beta1.Application
|
||||
@@ -106,6 +121,20 @@ var _ = Describe("Application Normal tests", func() {
|
||||
}, time.Second*5, time.Millisecond*500).Should(Succeed())
|
||||
}
|
||||
|
||||
verifyApplicationWorkflowSuspending := func(ns, appName string) {
|
||||
var testApp v1beta1.Application
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: appName}, &testApp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if testApp.Status.Phase != oamcomm.ApplicationWorkflowSuspending {
|
||||
return fmt.Errorf("application status wants %s, actually %s", oamcomm.ApplicationWorkflowSuspending, testApp.Status.Phase)
|
||||
}
|
||||
return nil
|
||||
}, 120*time.Second, time.Second).Should(BeNil())
|
||||
}
|
||||
|
||||
verifyWorkloadRunningExpected := func(workloadName string, replicas int32, image string) {
|
||||
var workload v1.Deployment
|
||||
By("Verify Workload running as expected")
|
||||
@@ -252,16 +281,108 @@ var _ = Describe("Application Normal tests", func() {
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
By("check application status")
|
||||
testApp := new(v1beta1.Application)
|
||||
Eventually(func() error {
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: newApp.Name}, testApp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if testApp.Status.Phase != oamcomm.ApplicationWorkflowSuspending {
|
||||
return fmt.Errorf("error application status wants %s, actually %s", oamcomm.ApplicationWorkflowSuspending, testApp.Status.Phase)
|
||||
}
|
||||
return nil
|
||||
}, 60*time.Second).Should(BeNil())
|
||||
verifyApplicationWorkflowSuspending(newApp.Namespace, newApp.Name)
|
||||
})
|
||||
|
||||
It("Test app with ServiceAccount", func() {
|
||||
By("Creating a ServiceAccount")
|
||||
const saName = "app-service-account"
|
||||
createServiceAccount(namespaceName, saName)
|
||||
|
||||
By("Creating Role and RoleBinding")
|
||||
const roleName = "worker"
|
||||
role := rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespaceName,
|
||||
Name: roleName,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{rbacv1.VerbAll},
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, &role)).Should(BeNil())
|
||||
|
||||
roleBinding := rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespaceName,
|
||||
Name: roleName + "-binding",
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Kind: "ServiceAccount",
|
||||
Name: saName,
|
||||
Namespace: namespaceName,
|
||||
},
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: rbacv1.GroupName,
|
||||
Kind: "Role",
|
||||
Name: roleName,
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, &roleBinding)).Should(BeNil())
|
||||
|
||||
By("Creating an application")
|
||||
var newApp v1beta1.Application
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
By("Checking an application status")
|
||||
verifyWorkloadRunningExpected("myweb", 1, "stefanprodan/podinfo:4.0.3")
|
||||
verifyComponentRevision("myweb", 1)
|
||||
})
|
||||
|
||||
It("Test app with ServiceAccount which has no permission for the component", func() {
|
||||
By("Creating a ServiceAccount")
|
||||
const saName = "dummy-service-account"
|
||||
createServiceAccount(namespaceName, saName)
|
||||
|
||||
By("Creating an application")
|
||||
var newApp v1beta1.Application
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
By("Checking an application status")
|
||||
verifyApplicationWorkflowSuspending(newApp.Namespace, newApp.Name)
|
||||
})
|
||||
|
||||
It("Test app with non-existence ServiceAccount", func() {
|
||||
By("Ensuring that given service account doesn't exists")
|
||||
const saName = "not-existing-service-account"
|
||||
sa := corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespaceName,
|
||||
Name: saName,
|
||||
},
|
||||
}
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Delete(ctx, &sa)
|
||||
},
|
||||
time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{}))
|
||||
|
||||
By("Creating an application")
|
||||
var newApp v1beta1.Application
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
By("Checking an application status")
|
||||
verifyApplicationWorkflowSuspending(newApp.Namespace, newApp.Name)
|
||||
})
|
||||
})
|
||||
|
||||
15
test/e2e-test/testdata/app/app11.yaml
vendored
Normal file
15
test/e2e-test/testdata/app/app11.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: core.oam.dev/v1beta1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: app-service-account-e2e
|
||||
annotations:
|
||||
app.oam.dev/service-account-name: default
|
||||
spec:
|
||||
components:
|
||||
- name: myweb
|
||||
type: worker
|
||||
properties:
|
||||
image: "stefanprodan/podinfo:4.0.3"
|
||||
cmd:
|
||||
- ./podinfo
|
||||
- stress-cpu=1
|
||||
Reference in New Issue
Block a user