istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/authz/provider.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package authz 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "istio.io/istio/pkg/config/protocol" 23 echoClient "istio.io/istio/pkg/test/echo" 24 "istio.io/istio/pkg/test/framework/components/echo" 25 "istio.io/istio/pkg/test/framework/components/echo/check" 26 "istio.io/istio/pkg/test/framework/components/echo/match" 27 ) 28 29 // API used by a Provider. Either HTTP or GRPC. 30 type API string 31 32 const ( 33 HTTP API = "http" 34 GRPC API = "grpc" 35 ) 36 37 // Provider for authz requests. 38 type Provider interface { 39 Name() string 40 41 // API used by this provider. 42 API() API 43 44 // IsProtocolSupported returns true if the given request protocol is supported by this provider. 45 IsProtocolSupported(protocol.Instance) bool 46 47 // IsTargetSupported returns true if the given target is supported by this provider. 48 IsTargetSupported(target echo.Target) bool 49 50 // MatchSupportedTargets returns a Matcher for filtering unsupported targets. 51 MatchSupportedTargets() match.Matcher 52 53 // Check returns an echo.Checker for validating response based on the request information. 54 Check(opts echo.CallOptions, expectAllowed bool) echo.Checker 55 } 56 57 var _ Provider = &providerImpl{} 58 59 type providerImpl struct { 60 name string 61 api API 62 protocolSupported func(protocol.Instance) bool 63 targetSupported func(echo.Target) bool 64 check func(opts echo.CallOptions, expectAllowed bool) echo.Checker 65 } 66 67 func (p *providerImpl) Name() string { 68 return p.name 69 } 70 71 func (p *providerImpl) API() API { 72 return p.api 73 } 74 75 func (p *providerImpl) IsProtocolSupported(i protocol.Instance) bool { 76 return p.protocolSupported(i) 77 } 78 79 func (p *providerImpl) IsTargetSupported(to echo.Target) bool { 80 return p.targetSupported(to) 81 } 82 83 func (p *providerImpl) MatchSupportedTargets() match.Matcher { 84 return func(i echo.Instance) bool { 85 return p.IsTargetSupported(i) 86 } 87 } 88 89 func (p *providerImpl) Check(opts echo.CallOptions, expectAllowed bool) echo.Checker { 90 return p.check(opts, expectAllowed) 91 } 92 93 func checkHTTP(opts echo.CallOptions, expectAllowed bool) echo.Checker { 94 override := opts.HTTP.Headers.Get(XExtAuthzAdditionalHeaderOverride) 95 var hType echoClient.HeaderType 96 if expectAllowed { 97 hType = echoClient.RequestHeader 98 } else { 99 hType = echoClient.ResponseHeader 100 } 101 headerChecker := check.And( 102 headerContains(hType, map[string][]string{ 103 XExtAuthzCheckReceived: {"additional-header-new-value", "additional-header-override-value"}, 104 XExtAuthzAdditionalHeaderOverride: {"additional-header-override-value"}, 105 }), 106 headerNotContains(hType, map[string][]string{ 107 XExtAuthzCheckReceived: {override}, 108 XExtAuthzAdditionalHeaderOverride: {override}, 109 })) 110 111 return checkRequest(opts, expectAllowed, headerChecker) 112 } 113 114 func checkGRPC(opts echo.CallOptions, expectAllowed bool) echo.Checker { 115 override := opts.HTTP.Headers.Get(XExtAuthzAdditionalHeaderOverride) 116 var hType echoClient.HeaderType 117 if expectAllowed { 118 hType = echoClient.RequestHeader 119 } else { 120 hType = echoClient.ResponseHeader 121 } 122 checkHeaders := check.And( 123 headerContains(hType, map[string][]string{ 124 XExtAuthzCheckReceived: {override}, 125 XExtAuthzAdditionalHeaderOverride: {GRPCAdditionalHeaderOverrideValue}, 126 }), 127 headerNotContains(hType, map[string][]string{ 128 XExtAuthzAdditionalHeaderOverride: {override}, 129 })) 130 131 return checkRequest(opts, expectAllowed, checkHeaders) 132 } 133 134 func checkRequest(opts echo.CallOptions, expectAllowed bool, checkHeaders echo.Checker) echo.Checker { 135 switch { 136 case opts.Port.Protocol.IsGRPC(): 137 switch opts.HTTP.Headers.Get(XExtAuthz) { 138 case XExtAuthzAllow: 139 return check.And(check.NoError(), checkHeaders) 140 default: 141 // Deny 142 return check.And(check.Forbidden(protocol.GRPC), 143 check.ErrorContains("desc = denied by ext_authz for not found header "+ 144 "`x-ext-authz: allow` in the request")) 145 } 146 case opts.Port.Protocol.IsTCP(): 147 if expectAllowed { 148 return check.NoError() 149 } 150 // Deny 151 return check.Forbidden(protocol.TCP) 152 default: 153 // HTTP 154 switch opts.HTTP.Headers.Get(XExtAuthz) { 155 case XExtAuthzAllow: 156 return check.And(check.NoError(), checkHeaders) 157 default: 158 // Deny 159 return check.And(check.Forbidden(protocol.HTTP), checkHeaders) 160 } 161 } 162 } 163 164 func headerContains(hType echoClient.HeaderType, expected map[string][]string) echo.Checker { 165 return check.Each(func(r echoClient.Response) error { 166 h := r.GetHeaders(hType) 167 for _, key := range sortKeys(expected) { 168 actual := h.Get(key) 169 170 for _, value := range expected[key] { 171 if !strings.Contains(actual, value) { 172 return fmt.Errorf("status code %s, expected %s header `%s` to contain `%s`, value=`%s`, raw content=%s", 173 r.Code, hType, key, value, actual, r.RawContent) 174 } 175 } 176 } 177 return nil 178 }) 179 } 180 181 func headerNotContains(hType echoClient.HeaderType, expected map[string][]string) echo.Checker { 182 return check.Each(func(r echoClient.Response) error { 183 h := r.GetHeaders(hType) 184 for _, key := range sortKeys(expected) { 185 actual := h.Get(key) 186 187 for _, value := range expected[key] { 188 if strings.Contains(actual, value) { 189 return fmt.Errorf("status code %s, expected %s header `%s` to not contain `%s`, value=`%s`, raw content=%s", 190 r.Code, hType, key, value, actual, r.RawContent) 191 } 192 } 193 } 194 return nil 195 }) 196 } 197 198 func sortKeys(v map[string][]string) []string { 199 out := make([]string, 0, len(v)) 200 for k := range v { 201 out = append(out, k) 202 } 203 sort.Strings(out) 204 return out 205 }