blob: 8de416163eff5546e1cb21799cf4a0bfba1ff809 [file] [log] [blame]
//go:build integ
// +build integ
// Copyright Istio 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 security
import (
"fmt"
"net/http"
"net/url"
"strings"
"testing"
)
import (
meshconfig "istio.io/api/mesh/v1alpha1"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test/framework"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/echo/check"
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/components/istio"
)
func supportedPercentEncode(i int) bool {
special := map[int]struct{}{
0x2d: {}, // -
0x2e: {}, // .
0x2f: {}, // /
0x5c: {}, // \
0x5f: {}, // _
0x7e: {}, // ~
}
if _, found := special[i]; found {
return true
}
if 0x30 <= i && i <= 0x39 {
// 0-9
return true
}
if 0x41 <= i && i <= 0x5a {
// A-Z
return true
}
if 0x61 <= i && i <= 0x7a {
// a-z
return true
}
return false
}
func TestNormalization(t *testing.T) {
type expect struct {
in, out string
}
var percentEncodedCases []expect
for i := 1; i <= 0xff; i++ {
input := fmt.Sprintf("/admin%%%.2x", i)
output := input
if supportedPercentEncode(i) {
var err error
output, err = url.PathUnescape(input)
switch i {
case 0x5c:
output = strings.ReplaceAll(output, `\`, `/`)
case 0x7e:
output = strings.ReplaceAll(output, `%7e`, `~`)
}
if err != nil {
t.Errorf("failed to unescape percent encoded path %s: %v", input, err)
}
}
percentEncodedCases = append(percentEncodedCases, expect{in: input, out: output})
}
framework.NewTest(t).
Features("security.normalization").
Run(func(t framework.TestContext) {
cases := []struct {
name string
ntype meshconfig.MeshConfig_ProxyPathNormalization_NormalizationType
expectations []expect
}{
{
"None",
meshconfig.MeshConfig_ProxyPathNormalization_NONE,
[]expect{
{"/", "/"},
{"/app#foo", "/app"},
{"/app/", "/app/"},
{"/app/../admin", "/app/../admin"},
{"/app", "/app"},
{"/app//", "/app//"},
{"/app/%2f", "/app/%2f"},
{"/app%2f/", "/app%2f/"},
{"/xyz%30..//abc", "/xyz%30..//abc"},
{"/app/%2E./admin", "/app/%2E./admin"},
{`/app\admin`, `/app\admin`},
{`/app/\/\/\admin`, `/app/\/\/\admin`},
{`/%2Fapp%5cadmin%5Cabc`, `/%2Fapp%5cadmin%5Cabc`},
{`/%5Capp%2f%5c%2F%2e%2e%2fadmin%5c\abc`, `/%5Capp%2f%5c%2F%2e%2e%2fadmin%5c\abc`},
{`/app//../admin`, `/app//../admin`},
{`/app//../../admin`, `/app//../../admin`},
},
},
{
"Base",
meshconfig.MeshConfig_ProxyPathNormalization_BASE,
[]expect{
{"/", "/"},
{"/app#foo", "/app"},
{"/app/", "/app/"},
{"/app/../admin", "/admin"},
{"/app", "/app"},
{"/app//", "/app//"},
{"/app/%2f", "/app/%2f"},
{"/app%2f/", "/app%2f/"},
{"/xyz%30..//abc", "/xyz0..//abc"},
{"/app/%2E./admin", "/admin"},
{`/app\admin`, `/app/admin`},
{`/app/\/\/\admin`, `/app//////admin`},
{`/%2Fapp%5cadmin%5Cabc`, `/%2Fapp%5cadmin%5Cabc`},
{`/%5Capp%2f%5c%2F%2e%2e%2fadmin%5c\abc`, `/%5Capp%2f%5c%2F..%2fadmin%5c/abc`},
{`/app//../admin`, `/app/admin`},
{`/app//../../admin`, `/admin`},
},
},
{
"MergeSlashes",
meshconfig.MeshConfig_ProxyPathNormalization_MERGE_SLASHES,
[]expect{
{"/", "/"},
{"/app#foo", "/app"},
{"/app/", "/app/"},
{"/app/../admin", "/admin"},
{"/app", "/app"},
{"/app//", "/app/"},
{"/app/%2f", "/app/%2f"},
{"/app%2f/", "/app%2f/"},
{"/xyz%30..//abc", "/xyz0../abc"},
{"/app/%2E./admin", "/admin"},
{`/app\admin`, `/app/admin`},
{`/app/\/\/\admin`, `/app/admin`},
{`/%2Fapp%5cadmin%5Cabc`, `/%2Fapp%5cadmin%5Cabc`},
{`/%5Capp%2f%5c%2F%2e%2e%2fadmin%5c\abc`, `/%5Capp%2f%5c%2F..%2fadmin%5c/abc`},
{`/app//../admin`, `/app/admin`},
{`/app//../../admin`, `/admin`},
},
},
{
"DecodeAndMergeSlashes",
meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES,
[]expect{
{"/", "/"},
{"/app#foo", "/app"},
{"/app/", "/app/"},
{"/app/../admin", "/admin"},
{"/app", "/app"},
{"/app//", "/app/"},
{"/app/%2f", "/app/"},
{"/app%2f/", "/app/"},
{"/xyz%30..//abc", "/xyz0../abc"},
{"/app/%2E./admin", "/admin"},
{`/app\admin`, `/app/admin`},
{`/app/\/\/\admin`, `/app/admin`},
{`/%2Fapp%5cadmin%5Cabc`, `/app/admin/abc`},
{`/%5Capp%2f%5c%2F%2e%2e%2fadmin%5c\abc`, `/app/admin/abc`},
{`/app//../admin`, `/app/admin`},
{`/app//../../admin`, `/admin`},
{`/%c0%2e%c0%2e/admin`, `/%c0.%c0./admin`},
{`/%c0%2fadmin`, `/%c0/admin`},
},
},
{
"NotNormalized",
meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES,
[]expect{
{`/0x2e0x2e/admin`, `/0x2e0x2e/admin`},
{`/0x2e0x2e0x2fadmin`, `/0x2e0x2e0x2fadmin`},
{`/0x2e0x2e0x5cadmin`, `/0x2e0x2e0x5cadmin`},
{`/0x2f0x2fadmin`, `/0x2f0x2fadmin`},
{`/0x5c0x5cadmin`, `/0x5c0x5cadmin`},
{`/%25c0%25ae%25c1%259cadmin`, `/%25c0%25ae%25c1%259cadmin`},
{`/%25c0%25ae%25c0%25ae/admin`, `/%25c0%25ae%25c0%25ae/admin`},
{`/%25c0%25afadmin`, `/%25c0%25afadmin`},
{`/%25c1%259cadmin`, `/%25c1%259cadmin`},
{`/%252e%252e/admin`, `/%252e%252e/admin`},
{`/%252e%252e%252fadmin`, `/%252e%252e%252fadmin`},
{`/%c1%9cadmin`, `/%c1%9cadmin`},
{`/%c0%ae%c0%ae/admin`, `/%c0%ae%c0%ae/admin`},
{`/%c0%afadmin`, `/%c0%afadmin`},
{`/.../admin`, `/.../admin`},
{`/..../admin`, `/..../admin`},
{`/..;/admin`, `/..;/admin`},
{`/;/admin`, `/;/admin`},
{`/admin;a=b`, `/admin;a=b`},
{`/admin;a=b/xyz`, `/admin;a=b/xyz`},
{`/admin,a=b/xyz`, `/admin,a=b/xyz`},
{`/Admin`, `/Admin`},
{`/ADMIN`, `/ADMIN`},
},
},
{
// Test percent encode cases from %01 to %ff. (%00 is covered in the invalid group below).
name: "PercentEncoded",
ntype: meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES,
expectations: percentEncodedCases,
},
{
"Invalid",
meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES,
[]expect{
{`/admin%00`, `400`},
},
},
}
for _, tt := range cases {
t.NewSubTest(tt.name).Run(func(t framework.TestContext) {
istio.PatchMeshConfigOrFail(t, ist.Settings().SystemNamespace, t.Clusters(), fmt.Sprintf(`
pathNormalization:
normalization: %v`, tt.ntype.String()))
for _, c := range apps.A {
for _, tt := range tt.expectations {
t.NewSubTest(tt.in).Run(func(t framework.TestContext) {
checker := check.URL(tt.out)
if tt.out == "400" {
checker = check.Status(http.StatusBadRequest)
}
c.CallOrFail(t, echo.CallOptions{
To: apps.B,
HTTP: echo.HTTP{
Path: tt.in,
},
Port: echo.Port{
Name: "http",
},
Check: checker,
})
})
}
}
})
}
})
}