blob: d1615c5c1d562676263d645d53655fd2e27a4de6 [file] [log] [blame]
//go:build integ
// +build integ
// Copyright Istio Authors. All Rights Reserved.
//
// 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 stackdriver
import (
"context"
"fmt"
"net/http"
"path/filepath"
"strings"
"testing"
"time"
)
import (
"golang.org/x/sync/errgroup"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test"
"github.com/apache/dubbo-go-pixiu/pkg/test/env"
"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/stackdriver"
"github.com/apache/dubbo-go-pixiu/pkg/test/scopes"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/retry"
"github.com/apache/dubbo-go-pixiu/tests/integration/telemetry"
)
const (
serverAuditAllLogEntry = "tests/integration/telemetry/stackdriver/testdata/security_authz_audit/server_audit_all_log.json.tmpl"
serverAuditFooLogEntry = "tests/integration/telemetry/stackdriver/testdata/security_authz_audit/server_audit_foo_log.json.tmpl"
serverAuditBarLogEntry = "tests/integration/telemetry/stackdriver/testdata/security_authz_audit/server_audit_bar_log.json.tmpl"
auditPolicyForLogEntry = "tests/integration/telemetry/stackdriver/testdata/security_authz_audit/v1beta1-audit-authorization-policy.yaml.tmpl"
)
// TestStackdriverAuditLogging testing Authz Policy can config stackdriver with audit policy
func TestStackdriverHTTPAuditLogging(t *testing.T) {
framework.NewTest(t).
Features("observability.telemetry.stackdriver").
Run(func(t framework.TestContext) {
g, _ := errgroup.WithContext(context.Background())
ns := EchoNsInst.Name()
args := map[string]string{
"Namespace": ns,
}
t.ConfigIstio().EvalFile(ns, args, filepath.Join(env.IstioSrc, auditPolicyForLogEntry)).ApplyOrFail(t)
t.Logf("Audit policy deployed to namespace %v", ns)
for _, cltInstance := range Clt {
cltInstance := cltInstance
scopes.Framework.Infof("Validating Audit policy and Telemetry for Cluster %v", cltInstance.Config().Cluster.StableName())
g.Go(func() error {
err := retry.UntilSuccess(func() error {
if err := sendTrafficForAudit(t, cltInstance); err != nil {
return err
}
t.Logf("Traffic sent to namespace %v", ns)
clName := cltInstance.Config().Cluster.Name()
trustDomain := telemetry.GetTrustDomain(cltInstance.Config().Cluster, Ist.Settings().SystemNamespace)
t.Logf("Collect Audit Log for cluster %v", clName)
var errs []string
errAuditFoo := ValidateLogs(t, filepath.Join(env.IstioSrc, serverAuditFooLogEntry), clName, trustDomain, stackdriver.ServerAuditLog)
if errAuditFoo == nil {
t.Logf("Foo Audit Log validated for cluster %v", clName)
} else {
errs = append(errs, errAuditFoo.Error())
}
errAuditBar := ValidateLogs(t, filepath.Join(env.IstioSrc, serverAuditBarLogEntry), clName, trustDomain, stackdriver.ServerAuditLog)
if errAuditBar == nil {
t.Logf("Bar Audit Log validated for cluster %v", clName)
} else {
errs = append(errs, errAuditBar.Error())
}
errAuditAll := ValidateLogs(t, filepath.Join(env.IstioSrc, serverAuditAllLogEntry), clName, trustDomain, stackdriver.ServerAuditLog)
if errAuditAll == nil {
t.Logf("All Audit Log validated for cluster %v", clName)
} else {
errs = append(errs, errAuditAll.Error())
}
entries, err := SDInst.ListLogEntries(stackdriver.ServerAuditLog, EchoNsInst.Name())
if err != nil {
errs = append(errs, err.Error())
} else {
for _, l := range entries {
if l.HttpRequest != nil && strings.HasSuffix(l.HttpRequest.RequestUrl, "audit-none") {
errs = append(errs, "unwanted audit log entry `/audit-none` received.")
}
}
}
if len(errs) == 0 {
return nil
}
return fmt.Errorf(strings.Join(errs, "\n"))
}, retry.Delay(5*time.Second), retry.Timeout(20*time.Second))
if err != nil {
return err
}
return nil
})
}
if err := g.Wait(); err != nil {
t.Fatalf("test failed: %v", err)
}
})
}
// send http requests with different header and path
func sendTrafficForAudit(t test.Failer, cltInstance echo.Instance) error {
t.Helper()
newOptions := func(headers http.Header, path string) echo.CallOptions {
return echo.CallOptions{
To: Srv,
Port: echo.Port{
Name: "http",
},
HTTP: echo.HTTP{
Headers: headers,
Path: path,
},
Count: telemetry.RequestCountMultipler * Srv.WorkloadsOrFail(t).Len(),
Retry: echo.Retry{
NoRetry: true,
},
}
}
opts := []echo.CallOptions{
// request will be logged if "request header" value and "to operation path" is matched with audit policy
// path "/audit-none" will be filtered by audit policy and will not be logged
newOptions(nil, "/audit-none"),
newOptions(map[string][]string{"X-Audit": {"foo"}}, "/audit-none"),
newOptions(map[string][]string{"x-Audit": {"bar"}}, "/audit-none"),
// Headers are case sensitive for this test framework. It requires capitalize the first letter of every word
newOptions(map[string][]string{"X-Header": {"bar"}}, "/foo"),
newOptions(map[string][]string{"X-Header": {"foo"}}, "/bar"),
newOptions(map[string][]string{"X-Header": {"bar"}}, "/bar"),
newOptions(map[string][]string{"X-Header": {"foo"}}, "/foo"),
// path "/audit-all" is matched in audit policy and all requests will be logged
newOptions(nil, "/audit-all"),
newOptions(map[string][]string{"X-Audit": {"foo"}}, "/audit-all"),
newOptions(map[string][]string{"X-Audit": {"bar"}}, "/audit-all"),
}
for _, opt := range opts {
if _, err := cltInstance.Call(opt); err != nil {
t.Logf("with call option %v got err %v", opt, err)
return err
}
}
return nil
}