Skip to content

Commit ba78a13

Browse files
authored
[1.12][TKG-29306] Filter user permission (#396)
Cherry pick #369 Filter permissions, remove the one that's been deprecated in corresponding AVI Controller version Add username to user object if it's enterprise license More logging Refactor makefile and dockerfile
1 parent cbffab9 commit ba78a13

File tree

8 files changed

+122
-33
lines changed

8 files changed

+122
-33
lines changed

Dockerfile

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Build the manager binary
5-
FROM golang:1.22-bullseye AS builder
5+
ARG GOLANG_IMAGE=golang:1.23-bullseye
6+
ARG BASE_IMAGE=gcr.io/distroless/static:nonroot
7+
FROM $GOLANG_IMAGE AS builder
68

79
WORKDIR /workspace
810
# Copy the Go Modules manifests
911
COPY go.mod go.mod
1012
COPY go.sum go.sum
1113
# cache deps before building and copying source so that we don't need to re-download as much
1214
# and so that source changes don't invalidate our downloaded layer
13-
ENV GOPROXY=https://goproxy.io,direct
15+
ARG GOPROXY=https://goproxy.io,direct
16+
17+
ENV GOPROXY=$GOPROXY
1418
RUN go mod download
1519

1620
# Copy the go source
@@ -24,7 +28,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager
2428

2529
# Use distroless as minimal base image to package the manager binary
2630
# Refer to https://github.com/GoogleContainerTools/distroless for more details
27-
FROM gcr.io/distroless/static:nonroot
31+
FROM $BASE_IMAGE
2832
WORKDIR /
2933
COPY --from=builder /workspace/manager .
3034
USER nonroot:nonroot

Makefile

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ IMG ?= ako-operator:$(IMAGE_TAG)
77
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
88
CRD_OPTIONS ?= "crd"
99

10+
GOPROXY ?= https://goproxy.io,direct
11+
1012
# downstream cache to avoid docker pull limitation
1113
CACHE_IMAGE_REGISTRY ?= harbor-repo.vmware.com/dockerhub-proxy-cache
1214

@@ -78,7 +80,11 @@ generate: $(CONTROLLER_GEN)
7880

7981
# Build the docker image
8082
docker-build:
81-
docker build . -t ${IMG} -f Dockerfile
83+
docker build . -t ${IMG} \
84+
--build-arg GOPROXY=$(GOPROXY) \
85+
--build-arg GOLANG_IMAGE=$(GOLANG_IMAGE) \
86+
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
87+
-f Dockerfile
8288

8389
# Push the docker image
8490
docker-push:

api/v1alpha1/constants.go

+2
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,6 @@ const (
5555
HAAVIInfraSettingAnnotationsKey = "aviinfrasetting.ako.vmware.com/name"
5656

5757
AKODeploymentConfigControllerName = "akodeploymentconfig-controller"
58+
59+
AVIControllerEnterpriseOnlyVersion = "v30.0.0"
5860
)

controllers/akodeploymentconfig/user/ako_role.go

+21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
package user
55

66
import (
7+
"github.com/go-logr/logr"
78
"github.com/vmware/alb-sdk/go/models"
9+
"golang.org/x/mod/semver"
810
"k8s.io/utils/ptr"
911
)
1012

@@ -320,3 +322,22 @@ var AkoRolePermission = []*models.Permission{
320322
Resource: ptr.To("PERMISSION_L4POLICYSET"),
321323
},
322324
}
325+
326+
var deprecatePermissionMap = map[string]string{
327+
"PERMISSION_PINGACCESSAGENT": "v30.2.1",
328+
}
329+
330+
func filterAkoRolePermissionByVersion(log logr.Logger, permissions []*models.Permission, version string) []*models.Permission {
331+
filtered := []*models.Permission{}
332+
for _, permission := range permissions {
333+
if v, ok := deprecatePermissionMap[*permission.Resource]; ok && semver.Compare(version, v) >= 0 {
334+
log.Info("Skip deprecated permission", "permission", *permission.Resource)
335+
// Skip deprecated permission
336+
continue
337+
}
338+
339+
filtered = append(filtered, permission)
340+
341+
}
342+
return filtered
343+
}

controllers/akodeploymentconfig/user/user_controller.go

+60-24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/go-logr/logr"
1010
"github.com/pkg/errors"
1111
"github.com/vmware/alb-sdk/go/models"
12+
"golang.org/x/mod/semver"
1213
corev1 "k8s.io/api/core/v1"
1314
apierrors "k8s.io/apimachinery/pkg/api/errors"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -241,7 +242,7 @@ func (r *AkoUserReconciler) reconcileAviUserNormal(
241242
aviPassword := string(mcSecret.Data["password"][:])
242243

243244
// ensures the AVI User exists and matches the mc secret
244-
if _, err = r.createOrUpdateAviUser(aviUsername, aviPassword, obj.Spec.Tenant.Name); err != nil {
245+
if err = r.createOrUpdateAviUser(log, aviUsername, aviPassword, obj.Spec.Tenant.Name); err != nil {
245246
log.Error(err, "Failed to create/update cluster avi user")
246247
return res, err
247248
} else {
@@ -263,21 +264,31 @@ func (r *AkoUserReconciler) getAVIControllerCA(ctx context.Context, obj *akoov1a
263264
}
264265

265266
// createOrUpdateAviUser create an avi user in avi controller
266-
func (r *AkoUserReconciler) createOrUpdateAviUser(aviUsername, aviPassword, tenantName string) (*models.User, error) {
267+
func (r *AkoUserReconciler) createOrUpdateAviUser(log logr.Logger, aviUsername, aviPassword, tenantName string) error {
268+
version, err := r.aviClient.GetControllerVersion()
269+
if err != nil {
270+
return err
271+
}
272+
// Add v prefix if not present so semver can parse it
273+
if len(version) > 0 && version[0] != 'v' {
274+
version = "v" + version
275+
}
276+
267277
aviUser, err := r.aviClient.UserGetByName(aviUsername)
268278
// user not found, create one
269279
if aviclient.IsAviUserNonExistentError(err) {
280+
log.Info("AVI User not found, creating a new user", "user", aviUsername)
270281
// for avi essential version the default tenant is admin
271282
if tenantName == "" {
272283
tenantName = "admin"
273284
}
274285
tenant, err := r.aviClient.TenantGet(tenantName)
275286
if err != nil {
276-
return nil, err
287+
return err
277288
}
278-
role, err := r.getOrCreateAkoUserRole(tenant.URL)
289+
role, err := r.getOrCreateAkoUserRole(log, tenant.URL, version)
279290
if err != nil {
280-
return nil, err
291+
return err
281292
}
282293
aviUser = &models.User{
283294
Name: &aviUsername,
@@ -291,50 +302,67 @@ func (r *AkoUserReconciler) createOrUpdateAviUser(aviUsername, aviPassword, tena
291302
},
292303
},
293304
}
294-
return r.aviClient.UserCreate(aviUser)
305+
// since v30.0.0, there is only enterprise edition
306+
if semver.Compare(version, akoov1alpha1.AVIControllerEnterpriseOnlyVersion) >= 0 {
307+
aviUser.Username = &aviUsername
308+
}
309+
if _, err := r.aviClient.UserCreate(aviUser); err != nil {
310+
return err
311+
}
312+
return nil
313+
} else if err != nil {
314+
log.Info("Failed to get AVI User", "user", aviUsername, "error", err)
315+
return err
295316
}
296317

297-
if err == nil {
298-
// ensure user's role align with latest essential permission when user found
299-
if _, err := r.ensureAkoUserRole(); err != nil {
300-
return nil, err
301-
}
302-
// Update the password when user found, this is needed when the AVI user was
303-
// created before the mc Secret. And this operation will sync
304-
// the User's password to be the same as mc Secret's
318+
// ensure user's role align with latest essential permission when user found
319+
if _, err := r.ensureAkoUserRole(log, version); err != nil {
320+
return err
321+
}
322+
// Update the password when user found, this is needed when the AVI user was
323+
// created before the mc Secret. And this operation will sync
324+
// the User's password to be the same as mc Secret's
325+
if aviUser.Password == nil || *aviUser.Password != aviPassword {
326+
log.Info("AVI User found, updating the password")
305327
aviUser.Password = &aviPassword
306-
return r.aviClient.UserUpdate(aviUser)
328+
if _, err := r.aviClient.UserUpdate(aviUser); err != nil {
329+
return err
330+
}
307331
}
308-
return nil, err
332+
return nil
309333
}
310334

311335
// getOrCreateAkoUserRole get ako user's role, create one if not exist
312-
func (r *AkoUserReconciler) getOrCreateAkoUserRole(roleTenantRef *string) (*models.Role, error) {
336+
func (r *AkoUserReconciler) getOrCreateAkoUserRole(log logr.Logger, roleTenantRef *string, version string) (*models.Role, error) {
337+
log.Info("Ensure AKO User Role")
313338
role, err := r.aviClient.RoleGetByName(akoov1alpha1.AkoUserRoleName)
314339
// not found ako user role, create one
315340
if aviclient.IsAviRoleNonExistentError(err) {
341+
log.V(3).Info("Creating AKO User Role since it's not found", "role", akoov1alpha1.AkoUserRoleName)
342+
log.Info("current avi version", "version", version)
316343
role = &models.Role{
317344
Name: ptr.To(akoov1alpha1.AkoUserRoleName),
318-
Privileges: AkoRolePermission,
345+
Privileges: filterAkoRolePermissionByVersion(log, AkoRolePermission, version),
319346
TenantRef: roleTenantRef,
320347
}
321348
return r.aviClient.RoleCreate(role)
322349
}
323350
if err == nil {
324-
return r.ensureAkoUserRole()
351+
return r.ensureAkoUserRole(log, version)
325352
}
326353
return role, err
327354
}
328355

329356
// ensureAkoUserRole ensure ako-essential-role has the latest permission
330-
func (r *AkoUserReconciler) ensureAkoUserRole() (*models.Role, error) {
357+
func (r *AkoUserReconciler) ensureAkoUserRole(log logr.Logger, version string) (*models.Role, error) {
331358
role, err := r.aviClient.RoleGetByName(akoov1alpha1.AkoUserRoleName)
332359
if err != nil {
333360
return role, err
334361
}
335362

336363
// check if role needs to be synced
337-
if syncAkoUserRole(role) {
364+
if syncAkoUserRole(role, version) {
365+
log.Info("Syncing AKO User Role with expected permissions")
338366
return r.aviClient.RoleUpdate(role)
339367
}
340368

@@ -346,15 +374,18 @@ func (r *AkoUserReconciler) ensureAkoUserRole() (*models.Role, error) {
346374
// Any additional permissions on the Role that are not part of
347375
// the desired AKO role are left as-is. It returns a bool
348376
// indicating whether the Role was changed.
349-
func syncAkoUserRole(role *models.Role) bool {
377+
// It also filters out permissions that are deprecated in the current AVI version.
378+
func syncAkoUserRole(role *models.Role, version string) bool {
350379
existingResources := sets.New[string]()
351380
updated := false
352381

353382
for i, permission := range role.Privileges {
354383
desiredType, ok := AkoRolePermissionMap[*permission.Resource]
355384
if !ok {
356-
// Existing AVI role has a permission that's not part of
357-
// the desired AKO role: leave it as-is.
385+
// Existing AVI role in AVI Controller has a permission that's not part of
386+
// the desired AKO role defined in AkoRolePermissionMap: leave it as-is.
387+
// Since those could come from a new AVI Controller version that AKO-Operator
388+
// Might not be aware of.
358389
continue
359390
}
360391

@@ -370,6 +401,11 @@ func syncAkoUserRole(role *models.Role) bool {
370401

371402
for resource, desiredType := range AkoRolePermissionMap {
372403
if !existingResources.Has(resource) {
404+
// Filter out permissions that are deprecated in the current AVI version.
405+
if deprecateVersion, ok := deprecatePermissionMap[resource]; ok && semver.Compare(version, deprecateVersion) >= 0 {
406+
// Skip adding deprecated permissions to the role
407+
continue
408+
}
373409
// Existing AVI role is missing a permission that's
374410
// part of the desired AKO role: add it.
375411
role.Privileges = append(role.Privileges, &models.Permission{

controllers/akodeploymentconfig/user/user_controller_test.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func SyncAkoUserRoleTest() {
139139
Specify("role has no permissions", func() {
140140
role := &models.Role{}
141141

142-
updated := syncAkoUserRole(role)
142+
updated := syncAkoUserRole(role, "v20.0.0")
143143
Expect(updated).To(BeTrue())
144144
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission)))
145145
Expect(role.Privileges).To(ContainElements(AkoRolePermission))
@@ -159,7 +159,7 @@ func SyncAkoUserRoleTest() {
159159
}
160160
}
161161

162-
updated := syncAkoUserRole(role)
162+
updated := syncAkoUserRole(role, "v20.0.0")
163163
Expect(updated).To(BeTrue())
164164
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission)))
165165
Expect(role.Privileges).To(ContainElements(AkoRolePermission))
@@ -179,7 +179,7 @@ func SyncAkoUserRoleTest() {
179179
})
180180
}
181181

182-
updated := syncAkoUserRole(role)
182+
updated := syncAkoUserRole(role, "v20.0.0")
183183
Expect(updated).To(BeTrue())
184184
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission)))
185185
Expect(role.Privileges).To(ContainElements(AkoRolePermission))
@@ -208,7 +208,7 @@ func SyncAkoUserRoleTest() {
208208
role.Privileges[i], role.Privileges[j] = role.Privileges[j], role.Privileges[i]
209209
})
210210

211-
updated := syncAkoUserRole(role)
211+
updated := syncAkoUserRole(role, "v20.0.0")
212212
Expect(updated).To(BeFalse())
213213
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission) + len(additionalPrivileges)))
214214
Expect(role.Privileges).To(ContainElements(AkoRolePermission))
@@ -246,10 +246,27 @@ func SyncAkoUserRoleTest() {
246246
role.Privileges[i], role.Privileges[j] = role.Privileges[j], role.Privileges[i]
247247
})
248248

249-
updated := syncAkoUserRole(role)
249+
updated := syncAkoUserRole(role, "v20.0.0")
250250
Expect(updated).To(BeTrue())
251251
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission) + len(additionalPrivileges)))
252252
Expect(role.Privileges).To(ContainElements(AkoRolePermission))
253253
Expect(role.Privileges).To(ContainElements(additionalPrivileges))
254254
})
255+
256+
Specify("AVI Controller is higher than 30.2.1", func() {
257+
role := &models.Role{}
258+
259+
updated := syncAkoUserRole(role, "v30.2.1")
260+
newPermissions := []*models.Permission{}
261+
for _, permission := range AkoRolePermission {
262+
if *permission.Resource == "PERMISSION_PINGACCESSAGENT" {
263+
continue
264+
}
265+
newPermissions = append(newPermissions, permission)
266+
267+
}
268+
Expect(updated).To(BeTrue())
269+
Expect(role.Privileges).To(HaveLen(len(AkoRolePermission) - 1))
270+
Expect(role.Privileges).To(ContainElements(newPermissions))
271+
})
255272
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/vmware-tanzu/tanzu-framework/apis/run v0.0.0-20221104044415-a462bbe793b9
1515
github.com/vmware/alb-sdk v0.0.0-20240502042605-947bfcf176dd
1616
github.com/vmware/load-balancer-and-ingress-services-for-kubernetes v0.0.0-20231012053946-537d99c1eba2
17+
golang.org/x/mod v0.14.0
1718
gopkg.in/yaml.v3 v3.0.1
1819
k8s.io/api v0.29.6
1920
k8s.io/apiextensions-apiserver v0.29.6

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs
205205
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
206206
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
207207
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
208+
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
209+
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
208210
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
209211
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
210212
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=

0 commit comments

Comments
 (0)