Skip to content

Commit

Permalink
ext-authz: add e2e tests for related header options in authz extensio…
Browse files Browse the repository at this point in the history
…n provider (#33650)
  • Loading branch information
yangminzhu authored Jun 28, 2021
1 parent c6d6782 commit 43ba7de
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 45 deletions.
2 changes: 1 addition & 1 deletion pkg/test/framework/components/echo/kube/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ spec:
{{- end }}
{{- if $.IncludeExtAuthz }}
- name: ext-authz
image: docker.io/istio/ext-authz:0.6
image: gcr.io/istio-testing/ext-authz:0.7
imagePullPolicy: {{ $.PullPolicy }}
ports:
- containerPort: 8000
Expand Down
14 changes: 14 additions & 0 deletions samples/extauthz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,17 @@ Note that `a` is just a default value for testing. It can be changed with the fl
$ kubectl delete -f ../sleep/sleep.yaml
$ kubectl delete -f ext-authz.yaml
```

## Advanced features

The Ext Authz server supports the following advanced features that are useful for testing:

- The ext authz server will add the `x-ext-authz-check-received` header to the user request. The content is the dump of
the check request it received from the ext-authz filter. This header is useful in verifying the ext-authz filter sending
the expected request to the ext authz server.

- The ext authz server will add (or override if it already exists) the header `x-ext-authz-additional-header-override` to
the user request. The value of the header depends on the type of ext-authz server.
The ext authz HTTP server will set it to the value of the same `x-ext-authz-additional-header-override` header in the
check request. The ext authz gRPC server will set it to the constant value `grpc-additional-header-override-value`.
This header is useful in verifying the header override behavior in the ext-authz filter.
2 changes: 1 addition & 1 deletion samples/extauthz/ext-authz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ spec:
app: ext-authz
spec:
containers:
- image: docker.io/istio/ext-authz:0.6
- image: gcr.io/istio-testing/ext-authz:0.7
imagePullPolicy: IfNotPresent
name: ext-authz
ports:
Expand Down
2 changes: 1 addition & 1 deletion samples/extauthz/local-ext-authz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ spec:
name: httpbin
ports:
- containerPort: 80
- image: docker.io/istio/ext-authz:0.5
- image: gcr.io/istio-testing/ext-authz:0.7
imagePullPolicy: IfNotPresent
name: ext-authz
ports:
Expand Down
4 changes: 2 additions & 2 deletions samples/extauthz/src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

HUB ?= docker.io/istio/ext-authz
TAG ?= 0.6
HUB ?= gcr.io/istio-testing/ext-authz
TAG ?= 0.7

build: main.go go.mod go.sum Dockerfile
docker build . -t $(HUB):$(TAG)
Expand Down
72 changes: 66 additions & 6 deletions samples/extauthz/src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
Expand All @@ -39,11 +40,14 @@ import (
)

const (
checkHeader = "x-ext-authz"
allowedValue = "allow"
resultHeader = "x-ext-authz-check-result"
resultAllowed = "allowed"
resultDenied = "denied"
checkHeader = "x-ext-authz"
allowedValue = "allow"
resultHeader = "x-ext-authz-check-result"
receivedHeader = "x-ext-authz-check-received"
overrideHeader = "x-ext-authz-additional-header-override"
overrideGRPCValue = "grpc-additional-header-override-value"
resultAllowed = "allowed"
resultDenied = "denied"
)

var (
Expand Down Expand Up @@ -87,6 +91,18 @@ func (s *extAuthzServerV2) Check(ctx context.Context, request *authv2.CheckReque
Value: resultAllowed,
},
},
{
Header: &corev2.HeaderValue{
Key: receivedHeader,
Value: request.GetAttributes().String(),
},
},
{
Header: &corev2.HeaderValue{
Key: overrideHeader,
Value: overrideGRPCValue,
},
},
},
},
},
Expand All @@ -107,6 +123,18 @@ func (s *extAuthzServerV2) Check(ctx context.Context, request *authv2.CheckReque
Value: resultDenied,
},
},
{
Header: &corev2.HeaderValue{
Key: receivedHeader,
Value: request.GetAttributes().String(),
},
},
{
Header: &corev2.HeaderValue{
Key: overrideHeader,
Value: overrideGRPCValue,
},
},
},
},
},
Expand All @@ -132,6 +160,18 @@ func (s *extAuthzServerV3) Check(ctx context.Context, request *authv3.CheckReque
Value: resultAllowed,
},
},
{
Header: &corev3.HeaderValue{
Key: receivedHeader,
Value: request.GetAttributes().String(),
},
},
{
Header: &corev3.HeaderValue{
Key: overrideHeader,
Value: overrideGRPCValue,
},
},
},
},
},
Expand All @@ -152,6 +192,18 @@ func (s *extAuthzServerV3) Check(ctx context.Context, request *authv3.CheckReque
Value: resultDenied,
},
},
{
Header: &corev3.HeaderValue{
Key: receivedHeader,
Value: request.GetAttributes().String(),
},
},
{
Header: &corev3.HeaderValue{
Key: overrideHeader,
Value: overrideGRPCValue,
},
},
},
},
},
Expand All @@ -161,14 +213,22 @@ func (s *extAuthzServerV3) Check(ctx context.Context, request *authv3.CheckReque

// ServeHTTP implements the HTTP check request.
func (s *ExtAuthzServer) ServeHTTP(response http.ResponseWriter, request *http.Request) {
l := fmt.Sprintf("%s %s%s, headers: %v\n", request.Method, request.Host, request.URL, request.Header)
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Printf("[HTTP] read body failed: %v", err)
}
l := fmt.Sprintf("%s %s%s, headers: %v, body: [%s]\n", request.Method, request.Host, request.URL, request.Header, body)
if allowedValue == request.Header.Get(checkHeader) {
log.Printf("[HTTP][allowed]: %s", l)
response.Header().Set(resultHeader, resultAllowed)
response.Header().Set(overrideHeader, request.Header.Get(overrideHeader))
response.Header().Set(receivedHeader, l)
response.WriteHeader(http.StatusOK)
} else {
log.Printf("[HTTP][denied]: %s", l)
response.Header().Set(resultHeader, resultDenied)
response.Header().Set(overrideHeader, request.Header.Get(overrideHeader))
response.Header().Set(receivedHeader, l)
response.WriteHeader(http.StatusForbidden)
response.Write([]byte(denyBody))
}
Expand Down
87 changes: 60 additions & 27 deletions tests/integration/security/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1367,7 +1367,8 @@ func TestAuthorization_Custom(t *testing.T) {
With(&x, echoConfig("x", false)).
BuildOrFail(t)

newTestCase := func(from, target echo.Instance, path, port string, header string, expectAllowed bool, scheme scheme.Instance) rbacUtil.TestCase {
newTestCase := func(from, target echo.Instance, path, port string, header string, expectAllowed bool,
expectHTTPResponse []rbacUtil.ExpectContains, scheme scheme.Instance) rbacUtil.TestCase {
return rbacUtil.TestCase{
Request: connection.Checker{
From: from,
Expand All @@ -1378,53 +1379,85 @@ func TestAuthorization_Custom(t *testing.T) {
Path: path,
},
},
Headers: map[string]string{"x-ext-authz": header},
ExpectAllowed: expectAllowed,
Headers: map[string]string{
"x-ext-authz": header,
"x-ext-authz-additional-header-override": "should-be-override",
},
ExpectAllowed: expectAllowed,
ExpectHTTPResponse: expectHTTPResponse,
}
}
expectHTTPResponse := []rbacUtil.ExpectContains{
{
// For ext authz HTTP server, we expect the check request to include the override value because it
// is configued in the ext-authz filter side.
Key: "X-Ext-Authz-Check-Received",
Values: []string{"additional-header-new-value", "additional-header-override-value"},
NotValues: []string{"should-be-override"},
},
{
Key: "X-Ext-Authz-Additional-Header-Override",
Values: []string{"additional-header-override-value"},
NotValues: []string{"should-be-override"},
},
}
expectGRPCResponse := []rbacUtil.ExpectContains{
{
// For ext authz gRPC server, we expect the check request to include the original override value
// because the override is not configurable in the ext-authz filter side.
Key: "X-Ext-Authz-Check-Received",
Values: []string{"should-be-override"},
},
{
Key: "X-Ext-Authz-Additional-Header-Override",
Values: []string{"grpc-additional-header-override-value"},
NotValues: []string{"should-be-override"},
},
}

// Path "/custom" is protected by ext-authz service and is accessible with the header `x-ext-authz: allow`.
// Path "/health" is not protected and is accessible to public.
cases := []rbacUtil.TestCase{
// workload b is using an ext-authz service in its own pod of HTTP API.
newTestCase(x, b, "/custom", "http", "allow", true, scheme.HTTP),
newTestCase(x, b, "/custom", "http", "deny", false, scheme.HTTP),
newTestCase(x, b, "/health", "http", "allow", true, scheme.HTTP),
newTestCase(x, b, "/health", "http", "deny", true, scheme.HTTP),
newTestCase(x, b, "/custom", "http", "allow", true, expectHTTPResponse, scheme.HTTP),
newTestCase(x, b, "/custom", "http", "deny", false, expectHTTPResponse, scheme.HTTP),
newTestCase(x, b, "/health", "http", "allow", true, nil, scheme.HTTP),
newTestCase(x, b, "/health", "http", "deny", true, nil, scheme.HTTP),

// workload c is using an ext-authz service in its own pod of gRPC API.
newTestCase(x, c, "/custom", "http", "allow", true, scheme.HTTP),
newTestCase(x, c, "/custom", "http", "deny", false, scheme.HTTP),
newTestCase(x, c, "/health", "http", "allow", true, scheme.HTTP),
newTestCase(x, c, "/health", "http", "deny", true, scheme.HTTP),
newTestCase(x, c, "/custom", "http", "allow", true, expectGRPCResponse, scheme.HTTP),
newTestCase(x, c, "/custom", "http", "deny", false, expectGRPCResponse, scheme.HTTP),
newTestCase(x, c, "/health", "http", "allow", true, nil, scheme.HTTP),
newTestCase(x, c, "/health", "http", "deny", true, nil, scheme.HTTP),

// workload d is using an local ext-authz service in the same pod as the application of HTTP API.
newTestCase(x, d, "/custom", "http", "allow", true, scheme.HTTP),
newTestCase(x, d, "/custom", "http", "deny", false, scheme.HTTP),
newTestCase(x, d, "/health", "http", "allow", true, scheme.HTTP),
newTestCase(x, d, "/health", "http", "deny", true, scheme.HTTP),
newTestCase(x, d, "/custom", "http", "allow", true, expectHTTPResponse, scheme.HTTP),
newTestCase(x, d, "/custom", "http", "deny", false, expectHTTPResponse, scheme.HTTP),
newTestCase(x, d, "/health", "http", "allow", true, nil, scheme.HTTP),
newTestCase(x, d, "/health", "http", "deny", true, nil, scheme.HTTP),

// workload e is using an local ext-authz service in the same pod as the application of gRPC API.
newTestCase(x, e, "/custom", "http", "allow", true, scheme.HTTP),
newTestCase(x, e, "/custom", "http", "deny", false, scheme.HTTP),
newTestCase(x, e, "/health", "http", "allow", true, scheme.HTTP),
newTestCase(x, e, "/health", "http", "deny", true, scheme.HTTP),
newTestCase(x, e, "/custom", "http", "allow", true, expectGRPCResponse, scheme.HTTP),
newTestCase(x, e, "/custom", "http", "deny", false, expectGRPCResponse, scheme.HTTP),
newTestCase(x, e, "/health", "http", "allow", true, nil, scheme.HTTP),
newTestCase(x, e, "/health", "http", "deny", true, nil, scheme.HTTP),

// workload f is using an ext-authz service in its own pod of TCP API.
newTestCase(a, f, "", "tcp-8092", "", true, scheme.TCP),
newTestCase(x, f, "", "tcp-8092", "", false, scheme.TCP),
newTestCase(a, f, "", "tcp-8093", "", true, scheme.TCP),
newTestCase(x, f, "", "tcp-8093", "", true, scheme.TCP),
newTestCase(a, f, "", "tcp-8092", "", true, nil, scheme.TCP),
newTestCase(x, f, "", "tcp-8092", "", false, nil, scheme.TCP),
newTestCase(a, f, "", "tcp-8093", "", true, nil, scheme.TCP),
newTestCase(x, f, "", "tcp-8093", "", true, nil, scheme.TCP),
}

rbacUtil.RunRBACTest(t, cases)

ingr := ist.IngressFor(t.Clusters().Default())
ingressCases := []rbacUtil.TestCase{
// workload g is using an ext-authz service in its own pod of HTTP API.
newTestCase(x, g, "/custom", "http", "allow", true, scheme.HTTP),
newTestCase(x, g, "/custom", "http", "deny", false, scheme.HTTP),
newTestCase(x, g, "/health", "http", "allow", true, scheme.HTTP),
newTestCase(x, g, "/health", "http", "deny", true, scheme.HTTP),
newTestCase(x, g, "/custom", "http", "allow", true, expectHTTPResponse, scheme.HTTP),
newTestCase(x, g, "/custom", "http", "deny", false, expectHTTPResponse, scheme.HTTP),
newTestCase(x, g, "/health", "http", "allow", true, nil, scheme.HTTP),
newTestCase(x, g, "/health", "http", "deny", true, nil, scheme.HTTP),
}
for _, tc := range ingressCases {
name := fmt.Sprintf("%s->%s:%s%s[%t]",
Expand Down
14 changes: 12 additions & 2 deletions tests/integration/security/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ meshConfig:
service: %q
port: 8000
pathPrefix: "/check"
includeHeadersInCheck: ["x-ext-authz"]
headersToUpstreamOnAllow: ["x-ext-authz-*"]
headersToDownstreamOnDeny: ["x-ext-authz-*"]
includeRequestHeadersInCheck: ["x-ext-authz"]
includeAdditionalHeadersInCheck:
x-ext-authz-additional-header-new: additional-header-new-value
x-ext-authz-additional-header-override: additional-header-override-value
- name: "ext-authz-grpc"
envoyExtAuthzGrpc:
service: %q
Expand All @@ -85,7 +90,12 @@ meshConfig:
service: ext-authz-http.local
port: 8000
pathPrefix: "/check"
includeHeadersInCheck: ["x-ext-authz"]
headersToUpstreamOnAllow: ["x-ext-authz-*"]
headersToDownstreamOnDeny: ["x-ext-authz-*"]
includeRequestHeadersInCheck: ["x-ext-authz"]
includeAdditionalHeadersInCheck:
x-ext-authz-additional-header-new: additional-header-new-value
x-ext-authz-additional-header-override: additional-header-override-value
- name: "ext-authz-grpc-local"
envoyExtAuthzGrpc:
service: ext-authz-grpc.local
Expand Down
Loading

0 comments on commit 43ba7de

Please sign in to comment.