Skip to content

Commit

Permalink
Add static validation to middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
katherinelc321 committed May 16, 2024
1 parent dd28057 commit 626717c
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 1 deletion.
1 change: 1 addition & 0 deletions frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func NewFrontend(logger *slog.Logger, listener net.Listener, emitter metrics.Emi
MiddlewareBody,
MiddlewareLowercase,
MiddlewareSystemData,
MiddlewareValidateStatic,
metricsMiddleware.Metrics(),
)

Expand Down
48 changes: 48 additions & 0 deletions frontend/middleware_validatestatic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"net/http"
"regexp"

uuid "github.com/google/uuid"

"github.com/Azure/ARO-HCP/internal/api"
"github.com/Azure/ARO-HCP/internal/api/arm"
)

// Referenced in https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftresources
var rxResourceGroupName = regexp.MustCompile(`^[a-zA-Z0-9_()-][a-zA-Z0-9_().-]{0,87}[a-zA-Z0-9_()-]$`)
var rxResourceName = regexp.MustCompile(`^[a-zA-Z0-9-]{3,24}$`)

func MiddlewareValidateStatic(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

subId := r.PathValue(PathSegmentSubscriptionID)
resourceGroupName := r.PathValue(PathSegmentResourceGroupName)
resourceName := r.PathValue(PathSegmentResourceName)

if subId != "" {
if uuid.Validate(subId) != nil {
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorCodeInvalidSubscriptionID, "", "The provided subscription identifier '%s' is malformed or invalid.", subId)
return
}
}

if resourceGroupName != "" {
if !rxResourceGroupName.MatchString(resourceGroupName) {
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorInvalidResourceGroupName, "", "Resource group '%s' is invalid.", resourceGroupName)
return
}
}

if resourceName != "" {
if !rxResourceName.MatchString(resourceName) {
arm.WriteError(w, http.StatusBadRequest, arm.CloudErrorInvalidResourceName, "", "The Resource '%s/%s' under resource group '%s' is invalid.", api.ResourceType, resourceName, resourceGroupName)
return
}
}

next(w, r)
}
107 changes: 107 additions & 0 deletions frontend/middleware_validatestatic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package main

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/Azure/ARO-HCP/internal/api/arm"
)

type CloudErrorContainer struct {
Error arm.CloudErrorBody `json:"error"`
}

func TestMiddlewareValidateStatic(t *testing.T) {
// This will act as the next handler if middleware validation passes
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) // indicate success
})

tests := []struct {
name string
path string
subscriptionID string
resourceGroupName string

resourceType string
resourceName string
operationsId string
expectedStatusCode int
expectedBody string
}{
{
name: "Valid request",
path: "/subscriptions/42d9eac4-d29a-4d6e-9e26-3439758b1491",
subscriptionID: "42d9eac4-d29a-4d6e-9e26-3439758b1491",
expectedStatusCode: http.StatusOK,
},
{
name: "Invalid subscription ID",
path: "/subscriptions/invalid!sub!id",
subscriptionID: "invalid!sub!id",
expectedStatusCode: http.StatusBadRequest,
expectedBody: "The provided subscription identifier 'invalid!sub!id' is malformed or invalid.",
},
{
name: "Invalid resource group name",
path: "/resourcegroups/resourcegroup!",
resourceGroupName: "resourcegroup!",
expectedStatusCode: http.StatusBadRequest,
expectedBody: "Resource group 'resourcegroup!' is invalid.",
},
{
name: "Invalid resource name",
path: "/resourcegroup/providers/microsoft.redhatopenshift/hcpopenshiftcluster/$",
resourceGroupName: "resourcegroup",
resourceType: "hcpOpenShiftClusters",
resourceName: "$",
expectedStatusCode: http.StatusBadRequest,
expectedBody: "The Resource 'Microsoft.RedHatOpenShift/hcpOpenShiftClusters/$' under resource group 'resourcegroup' is invalid.",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "http://example.com"+tc.path, nil)

// Use httptest.ResponseRecorder to record the response
w := httptest.NewRecorder()

req.SetPathValue(PathSegmentSubscriptionID, tc.subscriptionID)
req.SetPathValue(PathSegmentResourceGroupName, tc.resourceGroupName)
req.SetPathValue(PathSegmentResourceName, tc.resourceName)

// Execute the middleware
MiddlewareValidateStatic(w, req, nextHandler)

// Check the response status code
if status := w.Code; status != tc.expectedStatusCode {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tc.expectedStatusCode)
}

if tc.expectedStatusCode != http.StatusOK {

var resp CloudErrorContainer
body, err := io.ReadAll(http.MaxBytesReader(w, w.Result().Body, 4*megabyte))
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
err = json.Unmarshal(body, &resp)
if err != nil {
t.Fatalf("failed to unmarshal response body: %v", err)
}

// Check if the error message contains the expected text
if !strings.Contains(resp.Error.Message, tc.expectedBody) {
t.Errorf("handler returned unexpected body: got %v want %v",
resp.Error.Message, tc.expectedBody)
}
}
})
}
}
33 changes: 33 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs=
github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
Expand Down Expand Up @@ -25,6 +33,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
Expand Down Expand Up @@ -69,17 +79,40 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
Expand Down
5 changes: 5 additions & 0 deletions internal/api/arm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const (
CloudErrorCodeUnsupportedMediaType = "UnsupportedMediaType"
CloudErrorCodeNotFound = "NotFound"
CloudErrorInvalidSubscriptionState = "InvalidSubscriptionState"
CloudErrorCodeResourceNotFound = "ResourceNotFound"
CloudErrorCodeResourceGroupNotFound = "ResourceGroupNotFound"
CloudErrorCodeInvalidSubscriptionID = "InvalidSubscriptionID"
CloudErrorInvalidResourceName = "InvalidResourceName"
CloudErrorInvalidResourceGroupName = "InvalidResourceGroupName"
)

// CloudError represents a complete resource provider error.
Expand Down
4 changes: 3 additions & 1 deletion internal/api/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package api

import "slices"
import (
"slices"
)

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
Expand Down

0 comments on commit 626717c

Please sign in to comment.