Skip to content

Commit

Permalink
Implement mesh config pathNormalization
Browse files Browse the repository at this point in the history
  • Loading branch information
howardjohn committed Apr 26, 2021
1 parent 04d2c8b commit 749b8f8
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 13 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible
github.com/envoyproxy/go-control-plane v0.9.9-0.20210115003313-31f9241a16e6
github.com/envoyproxy/go-control-plane v0.9.9-0.20210420150223-d760b7f6014b
github.com/evanphx/json-patch v4.9.0+incompatible
github.com/evanphx/json-patch/v5 v5.1.0
github.com/fatih/color v1.10.0
Expand Down Expand Up @@ -99,8 +99,8 @@ require (
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
helm.sh/helm/v3 v3.4.2
honnef.co/go/tools v0.0.1-2020.1.5 // indirect
istio.io/api v0.0.0-20210423190824-95fdcf6a6234
istio.io/client-go v1.9.3-0.20210420212959-a7d9bf5db101
istio.io/api v0.0.0-20210419172736-e076ff10ec38
istio.io/client-go v1.9.2
istio.io/gogo-genproto v0.0.0-20210420211914-9cbf6943c732
istio.io/pkg v0.0.0-20201230223204-2d0a1c8bd9e5
k8s.io/api v0.20.1
Expand Down
15 changes: 7 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210115003313-31f9241a16e6 h1:AXDhr2eS+C/TIo2p+rNDesagHP2UFgO8OKHiOYt+sTE=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210115003313-31f9241a16e6/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210420150223-d760b7f6014b h1:TOooj2KErUY1emNhErCVC72Zhh/QDcJLoymu8EYMKRs=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210420150223-d760b7f6014b/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20190815234213-e83c0a1c26c8 h1:DM7gHzQfHwIj+St8zaPOI6iQEPAxOwIkskvw6s9rDaM=
Expand Down Expand Up @@ -1318,12 +1318,11 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k=
honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
istio.io/api v0.0.0-20190515205759-982e5c3888c6/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo=
istio.io/api v0.0.0-20210420211535-1c598ea4139c h1:SDcswV02XpOBtlWt3vhC+N3JvKS3qXZdL4zj83kHiwk=
istio.io/api v0.0.0-20210420211535-1c598ea4139c/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64=
istio.io/api v0.0.0-20210423190824-95fdcf6a6234 h1:CZNpc1u073/1lkrfdmyaDNdgN8VkHN3ahThmVj5TYvw=
istio.io/api v0.0.0-20210423190824-95fdcf6a6234/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64=
istio.io/client-go v1.9.3-0.20210420212959-a7d9bf5db101 h1:ChllPx3wwu2c5niFK/P+Bn/7zeq+HVpwYujbiKIv9Mk=
istio.io/client-go v1.9.3-0.20210420212959-a7d9bf5db101/go.mod h1:LZ8ZCa52HrZIiNbVbK0wC0Xf+Bc5dXbIwyk5izXe/9M=
istio.io/api v0.0.0-20210318170531-e6e017e575c5/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64=
istio.io/api v0.0.0-20210419172736-e076ff10ec38 h1:7qU0LFO2d419fuyTzdxhWq1MssPrvMKfd9sYlz8qUK4=
istio.io/api v0.0.0-20210419172736-e076ff10ec38/go.mod h1:88HN3o1fSD1jo+Z1WTLlJfMm9biopur6Ct9BFKjiB64=
istio.io/client-go v1.9.2 h1:oeZ5JWp7gjx+5/OrzGjTzgJj7ZquBFXsHWquJjLXTLA=
istio.io/client-go v1.9.2/go.mod h1:BfOsn8V/wjBKFqEheQqLqx1r3pjDeYPsQnTU5yWjb1Y=
istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs=
istio.io/gogo-genproto v0.0.0-20210420211914-9cbf6943c732 h1:3vjcx027LU3jpf2MqRj0ddbSpSkXg0FQNApgg0aZF4U=
istio.io/gogo-genproto v0.0.0-20210420211914-9cbf6943c732/go.mod h1:6BwTZRNbWS570wHX/uR1Wqk5e0157TofTAUMzT7N4+s=
Expand Down
2 changes: 1 addition & 1 deletion istio.deps
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"name": "PROXY_REPO_SHA",
"repoName": "proxy",
"file": "",
"lastStableSHA": "daadd2f2b671ea13684536299e97b819debad136"
"lastStableSHA": "66da2bf864bde982351ee0ca2cae0a4e931f923c"
}
]
18 changes: 17 additions & 1 deletion pilot/pkg/networking/core/v1alpha3/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -1622,7 +1622,23 @@ func buildHTTPConnectionManager(listenerOpts buildListenerOpts, httpOpts *httpLi
connectionManager.AccessLog = []*accesslog.AccessLog{}
connectionManager.HttpFilters = filters
connectionManager.StatPrefix = httpOpts.statPrefix
connectionManager.NormalizePath = proto.BoolTrue

// Setup normalization
connectionManager.PathWithEscapedSlashesAction = hcm.HttpConnectionManager_KEEP_UNCHANGED
switch listenerOpts.push.Mesh.GetPathNormalization().GetNormalization() {
case meshconfig.MeshConfig_ProxyPathNormalization_NONE:
connectionManager.NormalizePath = proto.BoolFalse
case meshconfig.MeshConfig_ProxyPathNormalization_BASE, meshconfig.MeshConfig_ProxyPathNormalization_DEFAULT:
connectionManager.NormalizePath = proto.BoolTrue
case meshconfig.MeshConfig_ProxyPathNormalization_MERGE_SLASHES:
connectionManager.NormalizePath = proto.BoolTrue
connectionManager.MergeSlashes = true
case meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES:
connectionManager.NormalizePath = proto.BoolTrue
connectionManager.MergeSlashes = true
connectionManager.PathWithEscapedSlashesAction = hcm.HttpConnectionManager_UNESCAPE_AND_FORWARD
}

if httpOpts.useRemoteAddress {
connectionManager.UseRemoteAddress = proto.BoolTrue
} else {
Expand Down
242 changes: 242 additions & 0 deletions tests/integration/security/normalization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// +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 (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"sync"
"testing"

"github.com/hashicorp/go-multierror"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

meshconfig "istio.io/api/mesh/v1alpha1"
"istio.io/istio/pkg/test/framework"
"istio.io/istio/pkg/test/framework/components/cluster"
"istio.io/istio/pkg/test/framework/components/echo"
"istio.io/istio/pkg/test/scopes"
"istio.io/istio/pkg/util/gogoprotomarshal"
)

func TestNormalization(t *testing.T) {
framework.NewTest(t).
Features("traffic.routing").
Run(func(t framework.TestContext) {
type expect struct {
in, out string
}
cases := []struct {
ntype meshconfig.MeshConfig_ProxyPathNormalization_NormalizationType
expectations []expect
}{
{
meshconfig.MeshConfig_ProxyPathNormalization_NONE,
[]expect{
{"/", "/"},
{"/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`},
},
},
{
meshconfig.MeshConfig_ProxyPathNormalization_BASE,
[]expect{
{"/", "/"},
{"/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`},
},
},
{
meshconfig.MeshConfig_ProxyPathNormalization_MERGE_SLASHES,
[]expect{
{"/", "/"},
{"/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`},
},
},
{
meshconfig.MeshConfig_ProxyPathNormalization_DECODE_AND_MERGE_SLASHES,
[]expect{
{"/", "/"},
{"/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`},
},
},
}
for _, tt := range cases {
t.NewSubTest(tt.ntype.String()).Run(func(t framework.TestContext) {
PatchMeshConfig(t, ist.Settings().IstioNamespace, t.Clusters(), fmt.Sprintf(`
pathNormalization:
normalization: %v`, tt.ntype.String()))
for _, c := range apps.A {
for _, dst := range []echo.Instance{apps.B[0], apps.Naked[0]} {
for _, tt := range tt.expectations {
t.NewSubTest(fmt.Sprintf("%s/%s", dst.Config().Service, tt.in)).Run(func(t framework.TestContext) {
c.CallWithRetryOrFail(t, echo.CallOptions{
Target: dst,
Path: tt.in,
PortName: "http",
Validator: echo.ExpectKey("URL", tt.out),
})
})
}
}
}
})
}
})
}

func PatchMeshConfig(t framework.TestContext, ns string, clusters cluster.Clusters, patch string) {
errG := multierror.Group{}
origCfg := map[string]string{}
mu := sync.RWMutex{}

cmName := "istio"
if rev := t.Settings().Revision; rev != "default" && rev != "" {
cmName += "-" + rev
}
for _, c := range clusters.Kube() {
c := c
errG.Go(func() error {
cm, err := c.CoreV1().ConfigMaps(ns).Get(context.TODO(), cmName, v1.GetOptions{})
if err != nil {
return err
}
mcYaml, ok := cm.Data["mesh"]
if !ok {
return fmt.Errorf("mesh config was missing in istio config map for %s", c.Name())
}
mu.Lock()
origCfg[c.Name()] = cm.Data["mesh"]
mu.Unlock()
mc := &meshconfig.MeshConfig{}
if err := gogoprotomarshal.ApplyYAML(mcYaml, mc); err != nil {
return err
}
if err := gogoprotomarshal.ApplyYAML(patch, mc); err != nil {
return err
}
cm.Data["mesh"], err = gogoprotomarshal.ToYAML(mc)
if err != nil {
return err
}
_, err = c.CoreV1().ConfigMaps(ns).Update(context.TODO(), cm, v1.UpdateOptions{})
if err != nil {
return err
}
scopes.Framework.Infof("patched %s meshconfig:\n%s", c.Name(), cm.Data["mesh"])
pl, err := c.CoreV1().Pods(ns).List(context.TODO(), v1.ListOptions{LabelSelector: "app=istiod"})
if err != nil {
return err
}
for _, pod := range pl.Items {
patchBytes := fmt.Sprintf(`{ "metadata": {"annotations": { "test.istio.io/mesh-config-hash": "%s" } } }`, hash(cm.Data["mesh"]))
// Trigger immediate kubelet resync, to avoid 1 min+ delay on update
// https://github.com/kubernetes/kubernetes/issues/30189
_, err := c.CoreV1().Pods(ns).Patch(context.TODO(), pod.Name,
types.MergePatchType, []byte(patchBytes), v1.PatchOptions{FieldManager: "istio-ci"})
if err != nil {
return fmt.Errorf("patch %v: %v", patchBytes, err)
}
}
return nil
})
}
t.WhenDone(func() error {
errG := multierror.Group{}
mu.RLock()
defer mu.RUnlock()
for cn, mcYaml := range origCfg {
cn, mcYaml := cn, mcYaml
c := clusters.GetByName(cn)
errG.Go(func() error {
cm, err := c.CoreV1().ConfigMaps(ns).Get(context.TODO(), cmName, v1.GetOptions{})
if err != nil {
return err
}
cm.Data["mesh"] = mcYaml
_, err = c.CoreV1().ConfigMaps(ns).Update(context.TODO(), cm, v1.UpdateOptions{})
return err
})
}
if err := errG.Wait().ErrorOrNil(); err != nil {
return fmt.Errorf("failed cleaning up cluster-local config: %v", err)
}
return nil
})
if err := errG.Wait().ErrorOrNil(); err != nil {
t.Fatal(err)
}
}

func hash(s string) string {
h := md5.New()
_, _ = io.WriteString(h, s)
return hex.EncodeToString(h.Sum(nil))
}

0 comments on commit 749b8f8

Please sign in to comment.