mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
Feat: multi-cluster authentication (#3713)
Signed-off-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
22
apis/core.oam.dev/common/register.go
Normal file
22
apis/core.oam.dev/common/register.go
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 common
|
||||
|
||||
const (
|
||||
// Group api group name
|
||||
Group = "core.oam.dev"
|
||||
)
|
||||
@@ -19,11 +19,13 @@ package v1alpha1
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
// Package type metadata.
|
||||
const (
|
||||
Group = "core.oam.dev"
|
||||
Group = common.Group
|
||||
Version = "v1alpha1"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,11 +21,13 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
// Package type metadata.
|
||||
const (
|
||||
Group = "core.oam.dev"
|
||||
Group = common.Group
|
||||
Version = "v1alpha2"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,11 +21,13 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
)
|
||||
|
||||
// Package type metadata.
|
||||
const (
|
||||
Group = "core.oam.dev"
|
||||
Group = common.Group
|
||||
Version = "v1beta1"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,13 @@ package types
|
||||
|
||||
import "github.com/oam-dev/kubevela/pkg/oam"
|
||||
|
||||
const (
|
||||
// KubeVelaName name of kubevela
|
||||
KubeVelaName = "kubevela"
|
||||
// VelaCoreName name of vela-core
|
||||
VelaCoreName = "vela-core"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultKubeVelaReleaseName defines the default name of KubeVela Release
|
||||
DefaultKubeVelaReleaseName = "kubevela"
|
||||
@@ -153,3 +160,8 @@ const (
|
||||
// TerrfaormComponentPrefix is the prefix of component type of terraform-xxx
|
||||
TerrfaormComponentPrefix = "terraform-"
|
||||
)
|
||||
|
||||
const (
|
||||
// ClusterGatewayAccessorGroup the group to impersonate which allows the access to the cluster-gateway
|
||||
ClusterGatewayAccessorGroup = "cluster-gateway-accessor"
|
||||
)
|
||||
|
||||
@@ -122,23 +122,27 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-core --wai
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `imagePullSecrets` | Image pull secrets | `[]` |
|
||||
| `nameOverride` | Override name | `""` |
|
||||
| `fullnameOverride` | Fullname override | `""` |
|
||||
| `serviceAccount.create` | Specifies whether a service account should be created | `true` |
|
||||
| `serviceAccount.annotations` | Annotations to add to the service account | `{}` |
|
||||
| `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | `nil` |
|
||||
| `nodeSelector` | Node selector | `{}` |
|
||||
| `tolerations` | Tolerations | `[]` |
|
||||
| `affinity` | Affinity | `{}` |
|
||||
| `rbac.create` | Specifies whether a RBAC role should be created | `true` |
|
||||
| `logDebug` | Enable debug logs for development purpose | `false` |
|
||||
| `logFilePath` | If non-empty, write log files in this path | `""` |
|
||||
| `logFileMaxSize` | Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. | `1024` |
|
||||
| `kubeClient.qps` | The qps for reconcile clients, default is 50 | `50` |
|
||||
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
|
||||
| Name | Description | Value |
|
||||
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| `imagePullSecrets` | Image pull secrets | `[]` |
|
||||
| `nameOverride` | Override name | `""` |
|
||||
| `fullnameOverride` | Fullname override | `""` |
|
||||
| `serviceAccount.create` | Specifies whether a service account should be created | `true` |
|
||||
| `serviceAccount.annotations` | Annotations to add to the service account | `{}` |
|
||||
| `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | `nil` |
|
||||
| `nodeSelector` | Node selector | `{}` |
|
||||
| `tolerations` | Tolerations | `[]` |
|
||||
| `affinity` | Affinity | `{}` |
|
||||
| `rbac.create` | Specifies whether a RBAC role should be created | `true` |
|
||||
| `logDebug` | Enable debug logs for development purpose | `false` |
|
||||
| `logFilePath` | If non-empty, write log files in this path | `""` |
|
||||
| `logFileMaxSize` | Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. | `1024` |
|
||||
| `kubeClient.qps` | The qps for reconcile clients, default is 50 | `50` |
|
||||
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
|
||||
| `authentication.enabled` | Enable authentication for application | `false` |
|
||||
| `authentication.withUser` | Application authentication will impersonate as the request User | `false` |
|
||||
| `authentication.defaultUser` | Application authentication will impersonate as the User if no user provided in Application | `kubevela:vela-core` |
|
||||
| `authentication.groupPattern` | Application authentication will impersonate as the request Group that matches the pattern | `kubevela:*` |
|
||||
|
||||
|
||||
## Uninstallation
|
||||
|
||||
@@ -120,6 +120,32 @@ webhooks:
|
||||
- UPDATE
|
||||
resources:
|
||||
- podspecworkloads
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ template "kubevela.name" . }}-webhook
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /mutating-core-oam-dev-v1beta1-applications
|
||||
{{- if .Values.admissionWebhooks.patch.enabled }}
|
||||
failurePolicy: Ignore
|
||||
{{- else }}
|
||||
failurePolicy: Fail
|
||||
{{- end }}
|
||||
name: mutating.core.oam.dev.v1beta1.applications
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
- v1
|
||||
sideEffects: None
|
||||
rules:
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- applications
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
|
||||
@@ -274,4 +274,30 @@ spec:
|
||||
runAsGroup: 2000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 2000
|
||||
{{ end }}
|
||||
---
|
||||
{{ if and .Values.multicluster.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-role
|
||||
rules:
|
||||
- apiGroups: [ "cluster.core.oam.dev" ]
|
||||
resources: [ "clustergateways/proxy" ]
|
||||
verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
|
||||
{{ end }}
|
||||
---
|
||||
{{ if and .Values.multicluster.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-role
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: cluster-gateway-accessor
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
{{ end }}
|
||||
@@ -25,6 +25,9 @@ subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kubevela.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- kind: Group
|
||||
name: core.oam.dev
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
# permissions to do leader election.
|
||||
@@ -169,6 +172,14 @@ spec:
|
||||
- "--max-workflow-wait-backoff-time={{ .Values.workflow.backoff.maxTime.waitState }}"
|
||||
- "--max-workflow-failed-backoff-time={{ .Values.workflow.backoff.maxTime.failedState }}"
|
||||
- "--max-workflow-step-error-retry-times={{ .Values.workflow.step.errorRetryTimes }}"
|
||||
- "--feature-gates=AuthenticateApplication={{- .Values.authentication.enabled | toString -}}"
|
||||
{{ if .Values.authentication.enabled }}
|
||||
{{ if .Values.authentication.withUser }}
|
||||
- "--authentication-with-user"
|
||||
{{ end }}
|
||||
- "--authentication-default-user={{ .Values.authentication.defaultUser }}"
|
||||
- "--authentication-group-pattern={{ .Values.authentication.groupPattern }}"
|
||||
{{ end }}
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: {{ quote .Values.image.pullPolicy }}
|
||||
resources:
|
||||
|
||||
@@ -232,3 +232,13 @@ admissionWebhooks:
|
||||
kubeClient:
|
||||
qps: 50
|
||||
burst: 100
|
||||
|
||||
## @param authentication.enabled Enable authentication for application
|
||||
## @param authentication.withUser Application authentication will impersonate as the request User
|
||||
## @param authentication.defaultUser Application authentication will impersonate as the User if no user provided in Application
|
||||
## @param authentication.groupPattern Application authentication will impersonate as the request Group that matches the pattern
|
||||
authentication:
|
||||
enabled: false
|
||||
withUser: false
|
||||
defaultUser: kubevela:vela-core
|
||||
groupPattern: kubevela:*
|
||||
|
||||
@@ -125,22 +125,26 @@ helm install --create-namespace -n vela-system kubevela kubevela/vela-minimal --
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Value |
|
||||
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `imagePullSecrets` | Image pull secrets | `[]` |
|
||||
| `nameOverride` | Override name | `""` |
|
||||
| `fullnameOverride` | Fullname override | `""` |
|
||||
| `serviceAccount.create` | Specifies whether a service account should be created | `true` |
|
||||
| `serviceAccount.annotations` | Annotations to add to the service account | `{}` |
|
||||
| `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | `nil` |
|
||||
| `nodeSelector` | Node selector | `{}` |
|
||||
| `tolerations` | Tolerations | `[]` |
|
||||
| `affinity` | Affinity | `{}` |
|
||||
| `rbac.create` | Specifies whether a RBAC role should be created | `true` |
|
||||
| `logDebug` | Enable debug logs for development purpose | `false` |
|
||||
| `logFilePath` | If non-empty, write log files in this path | `""` |
|
||||
| `logFileMaxSize` | Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. | `1024` |
|
||||
| `kubeClient.qps` | The qps for reconcile clients, default is 50 | `50` |
|
||||
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
|
||||
| Name | Description | Value |
|
||||
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------- |
|
||||
| `imagePullSecrets` | Image pull secrets | `[]` |
|
||||
| `nameOverride` | Override name | `""` |
|
||||
| `fullnameOverride` | Fullname override | `""` |
|
||||
| `serviceAccount.create` | Specifies whether a service account should be created | `true` |
|
||||
| `serviceAccount.annotations` | Annotations to add to the service account | `{}` |
|
||||
| `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | `nil` |
|
||||
| `nodeSelector` | Node selector | `{}` |
|
||||
| `tolerations` | Tolerations | `[]` |
|
||||
| `affinity` | Affinity | `{}` |
|
||||
| `rbac.create` | Specifies whether a RBAC role should be created | `true` |
|
||||
| `logDebug` | Enable debug logs for development purpose | `false` |
|
||||
| `logFilePath` | If non-empty, write log files in this path | `""` |
|
||||
| `logFileMaxSize` | Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. | `1024` |
|
||||
| `kubeClient.qps` | The qps for reconcile clients, default is 50 | `50` |
|
||||
| `kubeClient.burst` | The burst for reconcile clients, default is 100 | `100` |
|
||||
| `authentication.enabled` | Enable authentication for application | `false` |
|
||||
| `authentication.withUser` | Application authentication will impersonate as the request User | `false` |
|
||||
| `authentication.defaultUser` | Application authentication will impersonate as the User if no user provided in Application | `kubevela:vela-core` |
|
||||
| `authentication.groupPattern` | Application authentication will impersonate as the request Group that matches the pattern | `kubevela:*` |
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,32 @@ webhooks:
|
||||
- UPDATE
|
||||
resources:
|
||||
- podspecworkloads
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
name: {{ template "kubevela.name" . }}-webhook
|
||||
namespace: {{ .Release.Namespace }}
|
||||
path: /mutating-core-oam-dev-v1beta1-applications
|
||||
{{- if .Values.admissionWebhooks.patch.enabled }}
|
||||
failurePolicy: Ignore
|
||||
{{- else }}
|
||||
failurePolicy: Fail
|
||||
{{- end }}
|
||||
name: mutating.core.oam.dev.v1beta1.applications
|
||||
admissionReviewVersions:
|
||||
- v1beta1
|
||||
- v1
|
||||
sideEffects: None
|
||||
rules:
|
||||
- apiGroups:
|
||||
- core.oam.dev
|
||||
apiVersions:
|
||||
- v1beta1
|
||||
operations:
|
||||
- CREATE
|
||||
- UPDATE
|
||||
resources:
|
||||
- applications
|
||||
- clientConfig:
|
||||
caBundle: Cg==
|
||||
service:
|
||||
|
||||
@@ -188,4 +188,30 @@ spec:
|
||||
runAsGroup: 2000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 2000
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
---
|
||||
{{ if and .Values.multicluster.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-role
|
||||
rules:
|
||||
- apiGroups: [ "cluster.core.oam.dev" ]
|
||||
resources: [ "clustergateways/proxy" ]
|
||||
verbs: [ "get", "list", "watch", "create", "update", "patch", "delete" ]
|
||||
{{ end }}
|
||||
---
|
||||
{{ if and .Values.multicluster.enabled }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-rolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ include "kubevela.fullname" . }}:cluster-gateway-access-role
|
||||
subjects:
|
||||
- kind: Group
|
||||
name: cluster-gateway-accessor
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
{{ end }}
|
||||
@@ -27,6 +27,9 @@ subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "kubevela.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- kind: Group
|
||||
name: core.oam.dev
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
# permissions to do leader election.
|
||||
@@ -142,6 +145,14 @@ spec:
|
||||
- "--max-workflow-wait-backoff-time={{ .Values.workflow.backoff.maxTime.waitState }}"
|
||||
- "--max-workflow-failed-backoff-time={{ .Values.workflow.backoff.maxTime.failedState }}"
|
||||
- "--max-workflow-step-error-retry-times={{ .Values.workflow.step.errorRetryTimes }}"
|
||||
- "--feature-gates=AuthenticateApplication={{- .Values.authentication.enabled | toString -}}"
|
||||
{{ if .Values.authentication.enabled }}
|
||||
{{ if .Values.authentication.withUser }}
|
||||
- "--authentication-with-user"
|
||||
{{ end }}
|
||||
- "--authentication-default-user={{ .Values.authentication.defaultUser }}"
|
||||
- "--authentication-group-pattern={{ .Values.authentication.groupPattern }}"
|
||||
{{ end }}
|
||||
image: {{ .Values.imageRegistry }}{{ .Values.image.repository }}:{{ .Values.image.tag }}
|
||||
imagePullPolicy: {{ quote .Values.image.pullPolicy }}
|
||||
resources:
|
||||
|
||||
@@ -215,3 +215,13 @@ admissionWebhooks:
|
||||
kubeClient:
|
||||
qps: 50
|
||||
burst: 100
|
||||
|
||||
## @param authentication.enabled Enable authentication for application
|
||||
## @param authentication.withUser Application authentication will impersonate as the request User
|
||||
## @param authentication.defaultUser Application authentication will impersonate as the User if no user provided in Application
|
||||
## @param authentication.groupPattern Application authentication will impersonate as the request Group that matches the pattern
|
||||
authentication:
|
||||
enabled: false
|
||||
withUser: false
|
||||
defaultUser: kubevela:vela-core
|
||||
groupPattern: kubevela:*
|
||||
|
||||
@@ -36,6 +36,8 @@ import (
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
|
||||
apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
ctrlClient "github.com/oam-dev/kubevela/pkg/client"
|
||||
standardcontroller "github.com/oam-dev/kubevela/pkg/controller"
|
||||
@@ -50,6 +52,7 @@ import (
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcekeeper"
|
||||
pkgutils "github.com/oam-dev/kubevela/pkg/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
@@ -59,10 +62,6 @@ import (
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
const (
|
||||
kubevelaName = "kubevela"
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = common.Scheme
|
||||
waitSecretTimeout = 90 * time.Second
|
||||
@@ -202,10 +201,22 @@ func main() {
|
||||
klog.InfoS("Vela-Core init", "definition namespace", oam.SystemDefinitonNamespace)
|
||||
|
||||
restConfig := ctrl.GetConfigOrDie()
|
||||
restConfig.UserAgent = kubevelaName + "/" + version.GitRevision
|
||||
restConfig.UserAgent = types.KubeVelaName + "/" + version.GitRevision
|
||||
restConfig.QPS = float32(qps)
|
||||
restConfig.Burst = burst
|
||||
restConfig.Wrap(auth.NewImpersonatingRoundTripper)
|
||||
restConfig.Impersonate.UserName = types.VelaCoreName
|
||||
if sub := pkgutils.GetServiceAccountSubjectFromConfig(restConfig); sub != "" {
|
||||
restConfig.Impersonate.UserName = sub
|
||||
}
|
||||
restConfig.Impersonate.Groups = []string{apicommon.Group}
|
||||
klog.InfoS("Kubernetes Config Loaded",
|
||||
"UserAgent", restConfig.UserAgent,
|
||||
"QPS", restConfig.QPS,
|
||||
"Burst", restConfig.Burst,
|
||||
"Impersonate-User", restConfig.Impersonate.UserName,
|
||||
"Impersonate-Group", strings.Join(restConfig.Impersonate.Groups, ","),
|
||||
)
|
||||
|
||||
// wrapper the round tripper by multi cluster rewriter
|
||||
if enableClusterGateway {
|
||||
@@ -225,7 +236,7 @@ func main() {
|
||||
}
|
||||
ctrl.SetLogger(klogr.New())
|
||||
|
||||
leaderElectionID := util.GenerateLeaderElectionID(kubevelaName, controllerArgs.IgnoreAppWithoutControllerRequirement)
|
||||
leaderElectionID := util.GenerateLeaderElectionID(types.KubeVelaName, controllerArgs.IgnoreAppWithoutControllerRequirement)
|
||||
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
|
||||
2
go.mod
2
go.mod
@@ -264,7 +264,7 @@ require (
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
google.golang.org/grpc v1.38.0 // indirect
|
||||
|
||||
37
pkg/auth/flags.go
Normal file
37
pkg/auth/flags.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 auth
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultAuthenticateGroupPattern default value of groups patterns for authentication
|
||||
DefaultAuthenticateGroupPattern = types.KubeVelaName + ":*"
|
||||
)
|
||||
|
||||
var (
|
||||
// AuthenticationWithUser flag for enable the authentication of User in requests
|
||||
AuthenticationWithUser = false
|
||||
// AuthenticationDefaultUser the default user to use while no User is set in application
|
||||
AuthenticationDefaultUser = user.Anonymous
|
||||
// AuthenticationGroupPattern pattern for the authentication of Group in requests
|
||||
AuthenticationGroupPattern = DefaultAuthenticateGroupPattern
|
||||
)
|
||||
@@ -22,13 +22,17 @@ import (
|
||||
"net/http"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/client-go/transport"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
impersonateKey = "impersonate"
|
||||
)
|
||||
|
||||
var _ utilnet.RoundTripperWrapper = &impersonatingRoundTripper{}
|
||||
|
||||
type impersonatingRoundTripper struct {
|
||||
@@ -45,18 +49,22 @@ func NewImpersonatingRoundTripper(rt http.RoundTripper) http.RoundTripper {
|
||||
|
||||
func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
ctx := req.Context()
|
||||
|
||||
// Skip impersonation on non-local cluster requests
|
||||
if !multicluster.IsInLocalCluster(ctx) {
|
||||
return rt.rt.RoundTrip(req)
|
||||
req = req.Clone(ctx)
|
||||
userInfo, exists := request.UserFrom(ctx)
|
||||
if exists && userInfo != nil {
|
||||
if name := userInfo.GetName(); name != "" {
|
||||
req.Header.Set(transport.ImpersonateUserHeader, name)
|
||||
req.Header.Set(transport.ImpersonateGroupHeader, types.ClusterGatewayAccessorGroup)
|
||||
for _, group := range userInfo.GetGroups() {
|
||||
if group != types.ClusterGatewayAccessorGroup {
|
||||
req.Header.Add(transport.ImpersonateGroupHeader, group)
|
||||
}
|
||||
}
|
||||
q := req.URL.Query()
|
||||
q.Add(impersonateKey, "true")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
sa := oamutil.GetServiceAccountInContext(ctx)
|
||||
if sa == "" {
|
||||
return rt.rt.RoundTrip(req)
|
||||
}
|
||||
req = req.Clone(req.Context())
|
||||
req.Header.Set(transport.ImpersonateUserHeader, sa)
|
||||
return rt.rt.RoundTrip(req)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/transport"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
type testRoundTripper struct {
|
||||
@@ -42,30 +48,51 @@ func (rt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func TestImpersonatingRoundTripper(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AuthenticateApplication, true)()
|
||||
AuthenticationWithUser = true
|
||||
defer func() {
|
||||
AuthenticationWithUser = false
|
||||
}()
|
||||
testSets := map[string]struct {
|
||||
ctxFn func(context.Context) context.Context
|
||||
expected string
|
||||
ctxFn func(context.Context) context.Context
|
||||
expectedUser string
|
||||
expectedGroup []string
|
||||
}{
|
||||
"with service account": {
|
||||
ctxFn: func(ctx context.Context) context.Context {
|
||||
ctx = oamutil.SetServiceAccountInContext(ctx, "vela-system", "default")
|
||||
return ctx
|
||||
app := &v1beta1.Application{}
|
||||
app.SetNamespace("vela-system")
|
||||
v1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationApplicationServiceAccountName, "default")
|
||||
return ContextWithUserInfo(ctx, app)
|
||||
},
|
||||
expected: "system:serviceaccount:vela-system:default",
|
||||
expectedUser: "system:serviceaccount:vela-system:default",
|
||||
expectedGroup: []string{types.ClusterGatewayAccessorGroup},
|
||||
},
|
||||
"without service account and app": {
|
||||
ctxFn: func(ctx context.Context) context.Context {
|
||||
return ContextWithUserInfo(ctx, nil)
|
||||
},
|
||||
expectedUser: "",
|
||||
expectedGroup: []string{types.ClusterGatewayAccessorGroup},
|
||||
},
|
||||
"without service account": {
|
||||
ctxFn: func(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
return ContextWithUserInfo(ctx, &v1beta1.Application{})
|
||||
},
|
||||
expected: "",
|
||||
expectedUser: AuthenticationDefaultUser,
|
||||
expectedGroup: []string{types.ClusterGatewayAccessorGroup},
|
||||
},
|
||||
"ignore if non-local cluster request": {
|
||||
"with user and groups": {
|
||||
ctxFn: func(ctx context.Context) context.Context {
|
||||
ctx = multicluster.ContextWithClusterName(ctx, "test-cluster")
|
||||
ctx = oamutil.SetServiceAccountInContext(ctx, "vela-system", "default")
|
||||
return ctx
|
||||
app := &v1beta1.Application{}
|
||||
SetUserInfoInAnnotation(&app.ObjectMeta, authv1.UserInfo{
|
||||
Username: "username",
|
||||
Groups: []string{"kubevela:group1", "kubevela:group2"},
|
||||
})
|
||||
return ContextWithUserInfo(ctx, app)
|
||||
},
|
||||
expected: "",
|
||||
expectedUser: "username",
|
||||
expectedGroup: []string{types.ClusterGatewayAccessorGroup, "kubevela:group1", "kubevela:group2"},
|
||||
},
|
||||
}
|
||||
for name, ts := range testSets {
|
||||
@@ -76,12 +103,13 @@ func TestImpersonatingRoundTripper(t *testing.T) {
|
||||
rt := &testRoundTripper{}
|
||||
_, err := NewImpersonatingRoundTripper(rt).RoundTrip(req)
|
||||
require.NoError(t, err)
|
||||
if ts.expected == "" {
|
||||
if ts.expectedUser == "" {
|
||||
_, ok := rt.Request.Header[transport.ImpersonateUserHeader]
|
||||
require.False(t, ok)
|
||||
return
|
||||
}
|
||||
require.Equal(t, ts.expected, rt.Request.Header.Get(transport.ImpersonateUserHeader))
|
||||
require.Equal(t, ts.expectedUser, rt.Request.Header.Get(transport.ImpersonateUserHeader))
|
||||
require.Equal(t, ts.expectedGroup, rt.Request.Header.Values(transport.ImpersonateGroupHeader))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
92
pkg/auth/userinfo.go
Normal file
92
pkg/auth/userinfo.go
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/utils/strings/slices"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
const (
|
||||
groupSeparator = ","
|
||||
)
|
||||
|
||||
// ContextWithUserInfo inject username & group from app annotations into context
|
||||
// If serviceAccount is set and username is empty, identity will user the serviceAccount
|
||||
func ContextWithUserInfo(ctx context.Context, app *v1beta1.Application) context.Context {
|
||||
if app == nil {
|
||||
return ctx
|
||||
}
|
||||
return request.WithUser(ctx, GetUserInfoInAnnotation(&app.ObjectMeta))
|
||||
}
|
||||
|
||||
// SetUserInfoInAnnotation set username and group from userInfo into annotations
|
||||
// it will clear the existing service account annotation in avoid of permission leak
|
||||
func SetUserInfoInAnnotation(obj *metav1.ObjectMeta, userInfo authv1.UserInfo) {
|
||||
if AuthenticationWithUser {
|
||||
metav1.SetMetaDataAnnotation(obj, oam.AnnotationApplicationUsername, userInfo.Username)
|
||||
}
|
||||
re := regexp.MustCompile(strings.ReplaceAll(AuthenticationGroupPattern, "*", ".*"))
|
||||
var groups []string
|
||||
for _, group := range userInfo.Groups {
|
||||
if re.MatchString(group) {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
}
|
||||
metav1.SetMetaDataAnnotation(obj, oam.AnnotationApplicationGroup, strings.Join(groups, groupSeparator))
|
||||
}
|
||||
|
||||
// GetUserInfoInAnnotation extract user info from annotations
|
||||
// support compatibility for serviceAccount when name is empty
|
||||
func GetUserInfoInAnnotation(obj *metav1.ObjectMeta) user.Info {
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
|
||||
name := annotations[oam.AnnotationApplicationUsername]
|
||||
if serviceAccountName := annotations[oam.AnnotationApplicationServiceAccountName]; serviceAccountName != "" && name == "" {
|
||||
name = fmt.Sprintf("system:serviceaccount:%s:%s", obj.GetNamespace(), serviceAccountName)
|
||||
}
|
||||
|
||||
if name == "" && utilfeature.DefaultMutableFeatureGate.Enabled(features.AuthenticateApplication) {
|
||||
name = AuthenticationDefaultUser
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{
|
||||
Name: name,
|
||||
Groups: slices.Filter(
|
||||
[]string{},
|
||||
strings.Split(annotations[oam.AnnotationApplicationGroup], groupSeparator),
|
||||
func(s string) bool {
|
||||
return len(strings.TrimSpace(s)) > 0
|
||||
}),
|
||||
}
|
||||
}
|
||||
84
pkg/auth/userinfo_test.go
Normal file
84
pkg/auth/userinfo_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func TestContextWithUserInfo(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.AuthenticateApplication, true)()
|
||||
AuthenticationWithUser = true
|
||||
defer func() {
|
||||
AuthenticationWithUser = false
|
||||
}()
|
||||
testCases := map[string]struct {
|
||||
UserInfo *authv1.UserInfo
|
||||
ServiceAccount string
|
||||
ExpectUserInfo user.Info
|
||||
}{
|
||||
"empty": {
|
||||
ExpectUserInfo: &user.DefaultInfo{
|
||||
Name: user.Anonymous,
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
"service-account": {
|
||||
ServiceAccount: "sa",
|
||||
ExpectUserInfo: &user.DefaultInfo{
|
||||
Name: "system:serviceaccount:default:sa",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
"user-with-groups": {
|
||||
UserInfo: &authv1.UserInfo{
|
||||
Username: "user",
|
||||
Groups: []string{"group0", "kubevela:group1", "kubevela:group2"},
|
||||
},
|
||||
ServiceAccount: "override",
|
||||
ExpectUserInfo: &user.DefaultInfo{
|
||||
Name: "user",
|
||||
Groups: []string{"kubevela:group1", "kubevela:group2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tt := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := require.New(t)
|
||||
app := &v1beta1.Application{}
|
||||
app.SetNamespace("default")
|
||||
if tt.UserInfo != nil {
|
||||
SetUserInfoInAnnotation(&app.ObjectMeta, *tt.UserInfo)
|
||||
}
|
||||
if tt.ServiceAccount != "" {
|
||||
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationApplicationServiceAccountName, tt.ServiceAccount)
|
||||
}
|
||||
r.Equal(tt.ExpectUserInfo, GetUserInfoInAnnotation(&app.ObjectMeta))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package controller
|
||||
import (
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
ctrlClient "github.com/oam-dev/kubevela/pkg/client"
|
||||
"github.com/oam-dev/kubevela/pkg/component"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
@@ -52,4 +54,9 @@ func AddAdmissionFlags() {
|
||||
flag.BoolVar(&resourcekeeper.AllowCrossNamespaceResource, "allow-cross-namespace-resource", true, "If set to false, application can only apply resources within its namespace. Default to be true.")
|
||||
flag.StringVar(&resourcekeeper.AllowResourceTypes, "allow-resource-types", "", "If not empty, application can only apply resources with specified types. For example, --allow-resource-types=whitelist:Deployment.v1.apps,Job.v1.batch")
|
||||
flag.StringVar(&component.RefObjectsAvailableScope, "ref-objects-available-scope", component.RefObjectsAvailableScopeGlobal, "The available scope for ref-objects component to refer objects. Should be one of `namespace`, `cluster`, `global`")
|
||||
|
||||
// auth flags
|
||||
flag.BoolVar(&auth.AuthenticationWithUser, "authentication-with-user", false, "If set to true, User will be carried on application. Resource requests will be impersonated as the User.")
|
||||
flag.StringVar(&auth.AuthenticationDefaultUser, "authentication-default-user", types.KubeVelaName+":"+types.VelaCoreName, "The User to impersonate when the User of application is not set.")
|
||||
flag.StringVar(&auth.AuthenticationGroupPattern, "authentication-group-pattern", auth.DefaultAuthenticateGroupPattern, "During authentication, only groups with specified pattern will be carried on application. Resource requests will be impersonated as these selected groups.")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Compatibility Features
|
||||
|
||||
// DeprecatedPolicySpec enable the use of deprecated policy spec
|
||||
DeprecatedPolicySpec featuregate.Feature = "DeprecatedPolicySpec"
|
||||
// LegacyObjectTypeIdentifier enable the use of legacy object type identifier for selecting ref-object
|
||||
@@ -31,6 +33,11 @@ const (
|
||||
DeprecatedObjectLabelSelector featuregate.Feature = "DeprecatedObjectLabelSelector"
|
||||
// LegacyResourceTrackerGC enable the gc of legacy resource tracker in managed clusters
|
||||
LegacyResourceTrackerGC featuregate.Feature = "LegacyResourceTrackerGC"
|
||||
|
||||
// Edge Features
|
||||
|
||||
// AuthenticateApplication enable the authentication for application
|
||||
AuthenticateApplication featuregate.Feature = "AuthenticateApplication"
|
||||
)
|
||||
|
||||
var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
@@ -38,6 +45,7 @@ var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
LegacyObjectTypeIdentifier: {Default: false, PreRelease: featuregate.Alpha},
|
||||
DeprecatedObjectLabelSelector: {Default: false, PreRelease: featuregate.Alpha},
|
||||
LegacyResourceTrackerGC: {Default: true, PreRelease: featuregate.Alpha},
|
||||
AuthenticateApplication: {Default: false, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -36,14 +36,6 @@ func GetCluster(o client.Object) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetServiceAccountNameFromAnnotations extracts the service account name from the given object's annotations.
|
||||
func GetServiceAccountNameFromAnnotations(o client.Object) string {
|
||||
if annotations := o.GetAnnotations(); annotations != nil {
|
||||
return annotations[AnnotationServiceAccountName]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetPublishVersion get PublishVersion from object
|
||||
func GetPublishVersion(o client.Object) string {
|
||||
if annotations := o.GetAnnotations(); annotations != nil {
|
||||
|
||||
@@ -199,7 +199,13 @@ const (
|
||||
// AnnotationControllerRequirement indicates the controller version that can process the application.
|
||||
AnnotationControllerRequirement = "app.oam.dev/controller-version-require"
|
||||
|
||||
// AnnotationServiceAccountName indicates the name of the ServiceAccount to use to apply Components and run Workflow.
|
||||
// AnnotationApplicationServiceAccountName indicates the name of the ServiceAccount to use to apply Components and run Workflow.
|
||||
// ServiceAccount will be used in the local cluster only.
|
||||
AnnotationServiceAccountName = "app.oam.dev/service-account-name"
|
||||
AnnotationApplicationServiceAccountName = "app.oam.dev/service-account-name"
|
||||
|
||||
// AnnotationApplicationUsername indicates the username of the Application to use to apply resources
|
||||
AnnotationApplicationUsername = "app.oam.dev/username"
|
||||
|
||||
// AnnotationApplicationGroup indicates the group of the Application to use to apply resources
|
||||
AnnotationApplicationGroup = "app.oam.dev/group"
|
||||
)
|
||||
|
||||
@@ -303,26 +303,6 @@ func SetNamespaceInCtx(ctx context.Context, namespace string) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
// GetServiceAccountInContext returns the name of the service account which reconciles the app from the context.
|
||||
func GetServiceAccountInContext(ctx context.Context) string {
|
||||
if serviceAccount, ok := ctx.Value(ServiceAccountContextKey).(string); ok {
|
||||
return serviceAccount
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetServiceAccountInContext sets the name of the service account which reconciles the app.
|
||||
func SetServiceAccountInContext(ctx context.Context, namespace, name string) context.Context {
|
||||
if name == "" {
|
||||
// We may set `default` service account when the service account name is omitted.
|
||||
// However, setting `default` service account will break existing cluster-scoped applications,
|
||||
// so it would be better to give users a migration term.
|
||||
// TODO(devholic): Use `default` service account if omitted.
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, ServiceAccountContextKey, fmt.Sprintf("system:serviceaccount:%s:%s", namespace, name))
|
||||
}
|
||||
|
||||
// GetDefinition get definition from two level namespace
|
||||
func GetDefinition(ctx context.Context, cli client.Reader, definition client.Object, definitionName string) error {
|
||||
appNs := GetDefinitionNamespaceWithCtx(ctx)
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ func (h *resourceKeeper) delete(ctx context.Context, manifest *unstructured.Unst
|
||||
}
|
||||
// 2. delete manifests
|
||||
deleteCtx := multicluster.ContextWithClusterName(ctx, oam.GetCluster(manifest))
|
||||
deleteCtx = oamutil.SetServiceAccountInContext(deleteCtx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
deleteCtx = auth.ContextWithUserInfo(deleteCtx, h.app)
|
||||
if err = h.Client.Delete(deleteCtx, manifest); err != nil && !kerrors.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "cannot delete manifest, name: %s apiVersion: %s kind: %s", manifest.GetName(), manifest.GetAPIVersion(), manifest.GetKind())
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
@@ -127,7 +127,7 @@ func (h *resourceKeeper) record(ctx context.Context, manifests []*unstructured.U
|
||||
func (h *resourceKeeper) dispatch(ctx context.Context, manifests []*unstructured.Unstructured, applyOpts []apply.ApplyOption) error {
|
||||
errs := parallel.Run(func(manifest *unstructured.Unstructured) error {
|
||||
applyCtx := multicluster.ContextWithClusterName(ctx, oam.GetCluster(manifest))
|
||||
applyCtx = oamutil.SetServiceAccountInContext(applyCtx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
applyCtx = auth.ContextWithUserInfo(applyCtx, h.app)
|
||||
return h.applicator.Apply(applyCtx, manifest, applyOpts...)
|
||||
}, manifests, MaxDispatchConcurrent)
|
||||
return velaerrors.AggregateErrors(errs.([]error))
|
||||
|
||||
@@ -22,9 +22,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
)
|
||||
|
||||
@@ -43,7 +42,7 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
|
||||
if mr.Deleted {
|
||||
if entry.exists && entry.obj != nil && entry.obj.GetDeletionTimestamp() == nil {
|
||||
deleteCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
|
||||
deleteCtx = oamutil.SetServiceAccountInContext(deleteCtx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
deleteCtx = auth.ContextWithUserInfo(deleteCtx, h.app)
|
||||
if err := h.Client.Delete(deleteCtx, entry.obj); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete outdated resource %s in resourcetracker %s", mr.ResourceKey(), rt.Name)
|
||||
}
|
||||
@@ -58,7 +57,7 @@ func (h *resourceKeeper) StateKeep(ctx context.Context) error {
|
||||
return errors.Wrapf(err, "failed to decode resource %s from resourcetracker", mr.ResourceKey())
|
||||
}
|
||||
applyCtx := multicluster.ContextWithClusterName(ctx, mr.Cluster)
|
||||
applyCtx = oamutil.SetServiceAccountInContext(applyCtx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
applyCtx = auth.ContextWithUserInfo(applyCtx, h.app)
|
||||
if err = h.applicator.Apply(applyCtx, manifest, apply.MustBeControlledByApp(h.app)); err != nil {
|
||||
return errors.Wrapf(err, "failed to re-apply resource %s from resourcetracker %s", mr.ResourceKey(), rt.Name)
|
||||
}
|
||||
|
||||
29
pkg/utils/jwt.go
Normal file
29
pkg/utils/jwt.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 utils
|
||||
|
||||
import "github.com/form3tech-oss/jwt-go"
|
||||
|
||||
// GetTokenSubject extract the subject field from the jwt token
|
||||
func GetTokenSubject(token string) (string, error) {
|
||||
claims := jwt.MapClaims{}
|
||||
if _, err := jwt.ParseWithClaims(token, claims, nil); err != nil {
|
||||
return "", err
|
||||
}
|
||||
sub, _ := claims["sub"].(string)
|
||||
return sub, nil
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
@@ -106,3 +107,9 @@ func UpdateNamespace(ctx context.Context, kubeClient client.Client, name string,
|
||||
}
|
||||
return kubeClient.Update(ctx, &namespace)
|
||||
}
|
||||
|
||||
// GetServiceAccountSubjectFromConfig extract ServiceAccount subject from token
|
||||
func GetServiceAccountSubjectFromConfig(cfg *rest.Config) string {
|
||||
sub, _ := GetTokenSubject(cfg.BearerToken)
|
||||
return sub
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ func Register(mgr manager.Manager, args controller.Args) {
|
||||
traitdefinition.RegisterValidatingHandler(mgr, args)
|
||||
case "v0.3":
|
||||
application.RegisterValidatingHandler(mgr, args)
|
||||
application.RegisterMutatingHandler(mgr)
|
||||
componentdefinition.RegisterMutatingHandler(mgr, args)
|
||||
componentdefinition.RegisterValidatingHandler(mgr, args)
|
||||
traitdefinition.RegisterValidatingHandler(mgr, args)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/utils/strings/slices"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"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/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// MutatingHandler adding user info to application annotations
|
||||
type MutatingHandler struct {
|
||||
Decoder *admission.Decoder
|
||||
}
|
||||
|
||||
var _ admission.Handler = &MutatingHandler{}
|
||||
|
||||
// Handle mutate application
|
||||
func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
|
||||
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.AuthenticateApplication) {
|
||||
return admission.Patched("")
|
||||
}
|
||||
|
||||
if slices.Contains(req.UserInfo.Groups, common.Group) {
|
||||
return admission.Patched("")
|
||||
}
|
||||
|
||||
app := &v1beta1.Application{}
|
||||
if err := h.Decoder.Decode(req, app); err != nil {
|
||||
return admission.Errored(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
if metav1.HasAnnotation(app.ObjectMeta, oam.AnnotationApplicationServiceAccountName) {
|
||||
return admission.Errored(http.StatusBadRequest, errors.New("service-account annotation is not permitted when authentication enabled"))
|
||||
}
|
||||
auth.SetUserInfoInAnnotation(&app.ObjectMeta, req.UserInfo)
|
||||
|
||||
bs, err := json.Marshal(app)
|
||||
if err != nil {
|
||||
return admission.Errored(http.StatusInternalServerError, err)
|
||||
}
|
||||
return admission.PatchResponseFromRaw(req.AdmissionRequest.Object.Raw, bs)
|
||||
}
|
||||
|
||||
var _ admission.DecoderInjector = &MutatingHandler{}
|
||||
|
||||
// InjectDecoder .
|
||||
func (h *MutatingHandler) InjectDecoder(d *admission.Decoder) error {
|
||||
h.Decoder = d
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterMutatingHandler will register component mutation handler to the webhook
|
||||
func RegisterMutatingHandler(mgr manager.Manager) {
|
||||
server := mgr.GetWebhookServer()
|
||||
server.Register("/mutating-core-oam-dev-v1beta1-applications", &webhook.Admission{Handler: &MutatingHandler{}})
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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 application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
"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/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
var _ = Describe("Test Application Mutator", func() {
|
||||
|
||||
var mutatingHandler *MutatingHandler
|
||||
|
||||
BeforeEach(func() {
|
||||
mutatingHandler = &MutatingHandler{}
|
||||
Expect(mutatingHandler.InjectDecoder(decoder)).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test Application Mutator [no authentication]", func() {
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", features.AuthenticateApplication))).Should(Succeed())
|
||||
resp := mutatingHandler.Handle(ctx, admission.Request{})
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
Expect(resp.Patches).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test Application Mutator [ignore authentication]", func() {
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", features.AuthenticateApplication))).Should(Succeed())
|
||||
resp := mutatingHandler.Handle(ctx, admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
UserInfo: authv1.UserInfo{Groups: []string{common.Group}},
|
||||
}})
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
Expect(resp.Patches).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Test Application Mutator [bad request]", func() {
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", features.AuthenticateApplication))).Should(Succeed())
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: v1beta1.Group, Version: v1beta1.Version, Resource: "applications"},
|
||||
Object: runtime.RawExtension{Raw: []byte("bad request")},
|
||||
},
|
||||
}
|
||||
resp := mutatingHandler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
})
|
||||
|
||||
It("Test Application Mutator [bad request with service-account]", func() {
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", features.AuthenticateApplication))).Should(Succeed())
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: v1beta1.Group, Version: v1beta1.Version, Resource: "applications"},
|
||||
Object: runtime.RawExtension{Raw: []byte(`{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"name":"example","annotations":{"app.oam.dev/service-account-name":"default"}}}`)},
|
||||
},
|
||||
}
|
||||
resp := mutatingHandler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeFalse())
|
||||
Expect(resp.Result.Message).Should(ContainSubstring("service-account annotation is not permitted when authentication enabled"))
|
||||
})
|
||||
|
||||
It("Test Application Mutator [with patch]", func() {
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", features.AuthenticateApplication))).Should(Succeed())
|
||||
req := admission.Request{
|
||||
AdmissionRequest: admissionv1.AdmissionRequest{
|
||||
Operation: admissionv1.Create,
|
||||
Resource: metav1.GroupVersionResource{Group: v1beta1.Group, Version: v1beta1.Version, Resource: "applications"},
|
||||
Object: runtime.RawExtension{Raw: []byte(`{"apiVersion":"core.oam.dev/v1beta1","kind":"Application","metadata":{"name":"example"}}`)},
|
||||
UserInfo: authv1.UserInfo{
|
||||
Username: "example-user",
|
||||
Groups: []string{"kubevela:example-group1", "kubevela:example-group2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := mutatingHandler.Handle(ctx, req)
|
||||
Expect(resp.Allowed).Should(BeTrue())
|
||||
Expect(resp.Patches).Should(ContainElement(jsonpatch.JsonPatchOperation{
|
||||
Operation: "add",
|
||||
Path: "/metadata/annotations",
|
||||
Value: map[string]interface{}{
|
||||
oam.AnnotationApplicationGroup: "kubevela:example-group1,kubevela:example-group2",
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -25,11 +25,10 @@ import (
|
||||
|
||||
"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/auth"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model/value"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
wfContext "github.com/oam-dev/kubevela/pkg/workflow/context"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/providers"
|
||||
"github.com/oam-dev/kubevela/pkg/workflow/types"
|
||||
@@ -89,7 +88,7 @@ func (h *provider) Apply(ctx wfContext.Context, v *value.Value, act types.Action
|
||||
return err
|
||||
}
|
||||
deployCtx := multicluster.ContextWithClusterName(context.Background(), cluster)
|
||||
deployCtx = h.setServiceAccountInContext(deployCtx)
|
||||
deployCtx = auth.ContextWithUserInfo(deployCtx, h.app)
|
||||
if err := h.apply(deployCtx, cluster, common.WorkflowResourceCreator, workload); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,7 +123,7 @@ func (h *provider) ApplyInParallel(ctx wfContext.Context, v *value.Value, act ty
|
||||
return err
|
||||
}
|
||||
deployCtx := multicluster.ContextWithClusterName(context.Background(), cluster)
|
||||
deployCtx = h.setServiceAccountInContext(deployCtx)
|
||||
deployCtx = auth.ContextWithUserInfo(deployCtx, h.app)
|
||||
if err = h.apply(deployCtx, cluster, common.WorkflowResourceCreator, workloads...); err != nil {
|
||||
return v.FillObject(err, "err")
|
||||
}
|
||||
@@ -151,7 +150,7 @@ func (h *provider) Read(ctx wfContext.Context, v *value.Value, act types.Action)
|
||||
return err
|
||||
}
|
||||
readCtx := multicluster.ContextWithClusterName(context.Background(), cluster)
|
||||
readCtx = h.setServiceAccountInContext(readCtx)
|
||||
readCtx = auth.ContextWithUserInfo(readCtx, h.app)
|
||||
if err := h.cli.Get(readCtx, key, obj); err != nil {
|
||||
return v.FillObject(err.Error(), "err")
|
||||
}
|
||||
@@ -194,7 +193,7 @@ func (h *provider) List(ctx wfContext.Context, v *value.Value, act types.Action)
|
||||
client.MatchingLabels(filter.MatchingLabels),
|
||||
}
|
||||
readCtx := multicluster.ContextWithClusterName(context.Background(), cluster)
|
||||
readCtx = h.setServiceAccountInContext(readCtx)
|
||||
readCtx = auth.ContextWithUserInfo(readCtx, h.app)
|
||||
if err := h.cli.List(readCtx, list, listOpts...); err != nil {
|
||||
return v.FillObject(err.Error(), "err")
|
||||
}
|
||||
@@ -216,20 +215,13 @@ func (h *provider) Delete(ctx wfContext.Context, v *value.Value, act types.Actio
|
||||
return err
|
||||
}
|
||||
deleteCtx := multicluster.ContextWithClusterName(context.Background(), cluster)
|
||||
deleteCtx = h.setServiceAccountInContext(deleteCtx)
|
||||
deleteCtx = auth.ContextWithUserInfo(deleteCtx, h.app)
|
||||
if err := h.delete(deleteCtx, cluster, common.WorkflowResourceCreator, obj); err != nil {
|
||||
return v.FillObject(err.Error(), "err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *provider) setServiceAccountInContext(ctx context.Context) context.Context {
|
||||
if h.app == nil {
|
||||
return ctx
|
||||
}
|
||||
return oamutil.SetServiceAccountInContext(ctx, h.app.Namespace, oam.GetServiceAccountNameFromAnnotations(h.app))
|
||||
}
|
||||
|
||||
// Install register handlers to provider discover.
|
||||
func Install(p providers.Providers, app *v1beta1.Application, cli client.Client, apply Dispatcher, deleter Deleter) {
|
||||
if app != nil {
|
||||
|
||||
@@ -395,7 +395,7 @@ var _ = Describe("Application Normal tests", func() {
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
annotations[oam.AnnotationApplicationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
@@ -414,7 +414,7 @@ var _ = Describe("Application Normal tests", func() {
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
annotations[oam.AnnotationApplicationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
@@ -442,7 +442,7 @@ var _ = Describe("Application Normal tests", func() {
|
||||
Expect(common.ReadYamlToObject("testdata/app/app11.yaml", &newApp)).Should(BeNil())
|
||||
newApp.Namespace = namespaceName
|
||||
annotations := newApp.GetAnnotations()
|
||||
annotations[oam.AnnotationServiceAccountName] = saName
|
||||
annotations[oam.AnnotationApplicationServiceAccountName] = saName
|
||||
newApp.SetAnnotations(annotations)
|
||||
Expect(k8sClient.Create(ctx, &newApp)).Should(BeNil())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user