Skip to content

Commit fa90652

Browse files
committed
RHINENG-21230: update Kessel middleware after v2 permission changes
1 parent 2bc0949 commit fa90652

2 files changed

Lines changed: 41 additions & 139 deletions

File tree

manager/middlewares/kessel.go

Lines changed: 26 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
11
package middlewares
22

33
import (
4-
"app/base/rbac"
54
"app/base/utils"
65
"context"
76
"fmt"
87
"io"
98
"net/http"
109
"strings"
1110

12-
"github.com/bytedance/sonic"
1311
"github.com/gin-gonic/gin"
1412
"github.com/pkg/errors"
1513
"github.com/redhatinsights/platform-go-middlewares/identity"
16-
log "github.com/sirupsen/logrus"
1714
"google.golang.org/grpc"
1815

1916
"github.com/project-kessel/kessel-sdk-go/kessel/auth"
2017
kesselv2 "github.com/project-kessel/kessel-sdk-go/kessel/inventory/v1beta2"
2118
)
2219

23-
var granularPermissions = map[string]string{
24-
"TemplateSystemsUpdateHandler": "patch_template_edit",
25-
"TemplateSystemsDeleteHandler": "patch_template_edit",
26-
"SystemDeleteHandler": "patch_system_edit",
27-
}
28-
2920
var credentials = auth.NewOAuth2ClientCredentials(
3021
utils.CoreCfg.KesselAuthClientID,
3122
utils.CoreCfg.KesselAuthClientSecret,
@@ -72,102 +63,30 @@ func processWorkspaces(workspaces []*kesselv2.StreamedListObjectsResponse) (map[
7263
return map[string]string{utils.KeyGrouped: fmt.Sprintf("{%s}", strings.Join(groups, ","))}, nil
7364
}
7465

75-
func getToken(ctx context.Context) (string, error) {
76-
tokenReqCtx, tokenCtxCancel := context.WithCancel(ctx)
77-
defer tokenCtxCancel()
78-
res, err := credentials.GetToken(tokenReqCtx, auth.GetTokenOptions{})
79-
if err != nil {
80-
return "", err
81-
}
82-
return res.AccessToken, nil
83-
}
84-
85-
func getDefaultWorkspaceID(ctx context.Context, xrhid *identity.XRHID) (string, error) {
86-
workspaceReqCtx, workspaceCtxCancel := context.WithCancel(ctx)
87-
defer workspaceCtxCancel()
88-
89-
req, err := http.NewRequestWithContext(
90-
workspaceReqCtx, http.MethodGet, utils.CoreCfg.RbacURL+"/v2/workspaces/?type=default", nil)
91-
if err != nil {
92-
return "", errors.Wrap(err, "Failed to create a request for default workspaceID")
93-
}
94-
req.Header.Add("x-rh-rbac-org-id", xrhid.Identity.OrgID)
95-
96-
if utils.CoreCfg.KesselAuthEnabled {
97-
token, err := getToken(workspaceReqCtx)
98-
if err != nil {
99-
return "", errors.Wrap(err, "Request for RBAC token failed")
100-
}
101-
req.Header.Add("authorization", fmt.Sprintf("Bearer %s", token))
102-
}
103-
104-
httpRes, err := utils.CallAPI(&http.Client{}, req, log.IsLevelEnabled(log.TraceLevel))
105-
if err != nil {
106-
if httpRes != nil && httpRes.Body != nil {
107-
httpRes.Body.Close()
108-
}
109-
return "", errors.Wrap(err, "Request failed")
110-
}
111-
112-
var res rbac.DefaultWorkspaceResponse
113-
err = sonic.ConfigDefault.NewDecoder(httpRes.Body).Decode(&res)
114-
if err != nil && err != io.EOF {
115-
return "", errors.Wrap(err, "Response body reading failed")
66+
func buildPermission(c *gin.Context) string {
67+
permission := "patch_system_"
68+
nameSplit := strings.Split(c.HandlerName(), ".")
69+
handlerName := nameSplit[len(nameSplit)-1]
70+
if strings.HasPrefix(handlerName, "Template") {
71+
permission = "patch_template_"
11672
}
11773

118-
if len(res.Data) != 1 {
119-
return "", errors.New("RBAC returned an unexpected number of default workspaces")
74+
switch c.Request.Method {
75+
case http.MethodGet, http.MethodPost:
76+
permission += "view"
77+
case http.MethodPatch, http.MethodPut, http.MethodDelete:
78+
permission += "edit"
12079
}
12180

122-
return res.Data[0].ID, nil
81+
return permission
12382
}
12483

125-
func useCheckForUpdate(
84+
func useStreamedListObjects(
12685
c *gin.Context, client kesselv2.KesselInventoryServiceClient, xrhid *identity.XRHID, permission string,
127-
) error {
128-
checkReqCtx, checkCtxCancel := context.WithCancel(c)
129-
defer checkCtxCancel()
130-
131-
workspaceID, err := getDefaultWorkspaceID(checkReqCtx, xrhid)
132-
if err != nil {
133-
return errors.Wrap(err, "could not get default workspaceID")
134-
}
135-
136-
res, err := client.CheckForUpdate(checkReqCtx, &kesselv2.CheckForUpdateRequest{
137-
Object: &kesselv2.ResourceReference{
138-
ResourceType: "workspace",
139-
ResourceId: workspaceID,
140-
Reporter: &kesselv2.ReporterReference{
141-
Type: "rbac",
142-
},
143-
},
144-
Relation: permission,
145-
Subject: buildSubject(xrhid),
146-
})
147-
if err != nil {
148-
return errors.Wrap(err, "failed to communicate with Kessel")
149-
}
150-
151-
if res.Allowed != kesselv2.Allowed_ALLOWED_TRUE {
152-
c.AbortWithStatusJSON(http.StatusUnauthorized, utils.ErrorResponse{
153-
Error: "Missing permission", // does not have granular permission
154-
})
155-
}
156-
return nil
157-
}
158-
159-
func useStreamedListObjects(c *gin.Context, client kesselv2.KesselInventoryServiceClient, xrhid *identity.XRHID) error {
86+
) ([]*kesselv2.StreamedListObjectsResponse, error) {
16087
sloReqContext, sloContextCancel := context.WithCancel(c)
16188
defer sloContextCancel()
16289

163-
var permission string
164-
switch c.Request.Method {
165-
case http.MethodGet, http.MethodPost:
166-
permission = "patch_all_view"
167-
case http.MethodPut, http.MethodDelete:
168-
permission = "patch_all_edit"
169-
}
170-
17190
resourceType := "rbac"
17291
stream, err := client.StreamedListObjects(sloReqContext, &kesselv2.StreamedListObjectsRequest{
17392
ObjectType: &kesselv2.RepresentationType{
@@ -178,26 +97,18 @@ func useStreamedListObjects(c *gin.Context, client kesselv2.KesselInventoryServi
17897
Subject: buildSubject(xrhid),
17998
})
18099
if err != nil {
181-
return errors.Wrap(err, "failed to establish a gRPC stream with Kessel")
100+
return nil, errors.Wrap(err, "failed to establish a gRPC stream with Kessel")
182101
}
183102

184103
workspaces := make([]*kesselv2.StreamedListObjectsResponse, 0)
185104
for res, err := stream.Recv(); err != io.EOF; res, err = stream.Recv() {
186105
if err != nil {
187-
return errors.Wrap(err, "failed to receive all from Kessel")
106+
return nil, errors.Wrap(err, "failed to receive all from Kessel")
188107
}
189108
workspaces = append(workspaces, res)
190109
}
191110

192-
inventoryGroups, err := processWorkspaces(workspaces)
193-
if err != nil {
194-
utils.LogError("err", err.Error(), "processWorkspaces")
195-
c.AbortWithStatusJSON(http.StatusUnauthorized, utils.ErrorResponse{
196-
Error: "You don't have access to this application",
197-
})
198-
}
199-
c.Set(utils.KeyInventoryGroups, inventoryGroups)
200-
return nil
111+
return workspaces, nil
201112
}
202113

203114
func hasPermissionKessel(c *gin.Context) {
@@ -216,24 +127,19 @@ func hasPermissionKessel(c *gin.Context) {
216127
c.AbortWithStatusJSON(http.StatusUnauthorized, utils.ErrorResponse{Error: "Invalid x-rh-identity header"})
217128
}
218129

219-
// Require granular permission if set for handler
220-
nameSplit := strings.Split(c.HandlerName(), ".")
221-
handlerName := nameSplit[len(nameSplit)-1]
222-
if permission, has := granularPermissions[handlerName]; has {
223-
err = useCheckForUpdate(c, client, xrhid, permission)
224-
if err != nil {
225-
utils.LogError("err", err.Error(), "useCheckForUpdate failed")
226-
c.AbortWithStatus(http.StatusInternalServerError)
227-
}
228-
return
229-
}
230-
231-
// Require method-derived permission otherwise
232-
err = useStreamedListObjects(c, client, xrhid)
130+
permission := buildPermission(c)
131+
workspaces, err := useStreamedListObjects(c, client, xrhid, permission)
233132
if err != nil {
234133
utils.LogError("err", err.Error(), "useStreamedListObjects failed")
235134
c.AbortWithStatus(http.StatusInternalServerError)
236135
}
136+
137+
inventoryGroups, err := processWorkspaces(workspaces)
138+
if err != nil {
139+
utils.LogError("err", err.Error(), "processWorkspaces")
140+
c.AbortWithStatusJSON(http.StatusUnauthorized, utils.ErrorResponse{Error: "Missing permission"})
141+
}
142+
c.Set(utils.KeyInventoryGroups, inventoryGroups)
237143
}
238144

239145
func Kessel() gin.HandlerFunc {

manager/middlewares/kessel_test.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package middlewares
22

33
import (
44
"app/base/utils"
5-
"context"
65
"fmt"
76
"net/http"
87
"strconv"
@@ -15,6 +14,7 @@ import (
1514
kesselv2 "github.com/project-kessel/kessel-sdk-go/kessel/inventory/v1beta2"
1615
"github.com/redhatinsights/platform-go-middlewares/identity"
1716
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
1818
)
1919

2020
func TestSetupClient(t *testing.T) {
@@ -78,31 +78,24 @@ func TestProcessWorkspaces(t *testing.T) {
7878
}
7979
}
8080

81-
func TestGetDefaultWorkspaceID(t *testing.T) {
82-
workspaceID, err := getDefaultWorkspaceID(context.Background(), mockXRHID())
83-
if assert.NoError(t, err) {
84-
assert.NotEqual(t, "", workspaceID)
85-
}
86-
}
87-
88-
func TestUseCheckForUpdate(t *testing.T) {
89-
client, conn := mockClient(t)
90-
defer conn.Close()
81+
func TestBuildPermission(t *testing.T) {
82+
c := &gin.Context{Request: &http.Request{Method: http.MethodGet}}
83+
permission := buildPermission(c)
84+
assert.Equal(t, "patch_system_view", permission)
9185

92-
err := useCheckForUpdate(&gin.Context{}, client, mockXRHID(), "patch_template_view")
93-
assert.Nil(t, err)
86+
c = &gin.Context{Request: &http.Request{Method: http.MethodPut}}
87+
permission = buildPermission(c)
88+
assert.Equal(t, "patch_system_edit", permission)
9489
}
9590

9691
func TestUseStreamedListObjects(t *testing.T) {
9792
client, conn := mockClient(t)
9893
defer conn.Close()
9994

10095
c := &gin.Context{Request: &http.Request{Method: http.MethodGet}}
101-
err := useStreamedListObjects(c, client, mockXRHID())
96+
workspaces, err := useStreamedListObjects(c, client, mockXRHID(), "demo_permission")
10297
if assert.NoError(t, err) {
103-
inventoryGroups, found := c.Get(utils.KeyInventoryGroups)
104-
assert.True(t, found)
105-
assert.NotEqual(t, "", inventoryGroups)
98+
assert.Equal(t, 1, len(workspaces))
10699
}
107100
}
108101

@@ -111,8 +104,11 @@ func TestHasPermissionKessel(t *testing.T) {
111104
c.Request.Header.Set("x-rh-identity", "eyJlbnRpdGxlbWVudHMiOnsiaW5zaWdodHMiOnsiaXNfZW50aXRsZWQiOnRydWV9LCJjb3N0X21hbmFnZW1lbnQiOnsiaXNfZW50aXRsZWQiOnRydWV9LCJhbnNpYmxlIjp7ImlzX2VudGl0bGVkIjp0cnVlfSwib3BlbnNoaWZ0Ijp7ImlzX2VudGl0bGVkIjp0cnVlfSwic21hcnRfbWFuYWdlbWVudCI6eyJpc19lbnRpdGxlZCI6dHJ1ZX0sIm1pZ3JhdGlvbnMiOnsiaXNfZW50aXRsZWQiOnRydWV9fSwiaWRlbnRpdHkiOnsiaW50ZXJuYWwiOnsiYXV0aF90aW1lIjoyOTksImF1dGhfdHlwZSI6ImJhc2ljLWF1dGgiLCJvcmdfaWQiOiIxMTc4OTc3MiJ9LCJhY2NvdW50X251bWJlciI6IjYwODk3MTkiLCJ1c2VyIjp7ImZpcnN0X25hbWUiOiJJbnNpZ2h0cyIsImlzX2FjdGl2ZSI6dHJ1ZSwiaXNfaW50ZXJuYWwiOmZhbHNlLCJsYXN0X25hbWUiOiJRQSIsImxvY2FsZSI6ImVuX1VTIiwiaXNfb3JnX2FkbWluIjp0cnVlLCJ1c2VybmFtZSI6Imluc2lnaHRzLXFhIiwiZW1haWwiOiJqbmVlZGxlK3FhQHJlZGhhdC5jb20ifSwidHlwZSI6IlVzZXIifX0=") //nolint:lll
112105

113106
hasPermissionKessel(c)
114-
_, exists := c.Get(utils.KeyInventoryGroups)
115-
assert.True(t, exists)
107+
inventoryGroups, found := c.Get(utils.KeyInventoryGroups)
108+
require.True(t, found)
109+
inventoryGroupMap, ok := (inventoryGroups).(map[string]string)
110+
require.True(t, ok)
111+
assert.Equal(t, `{"[{\"id\":\"inventory-group-1\"}]"}`, inventoryGroupMap[utils.KeyGrouped])
116112
}
117113

118114
func mockXRHID() *identity.XRHID {

0 commit comments

Comments
 (0)