Complete adapter tests. Check in a sample config.
diff --git a/Makefile b/Makefile index 364ac8c..ef8654f 100644 --- a/Makefile +++ b/Makefile
@@ -4,9 +4,13 @@ test: bazel test ... --test_output=all +clean: + bazel clean + checkfmt: (cd adapter; ../tools/checkfmt.sh) (cd common; ../tools/checkfmt.sh) + (cd cmd/mockserver; ../../tools/checkfmt.sh) (cd mock; ../tools/checkfmt.sh) presubmit: checkfmt test
diff --git a/README.md b/README.md index 86cfed4..851eb64 100644 --- a/README.md +++ b/README.md
@@ -1,7 +1,46 @@ # Istio Apigee Adapter -This workspace holds an apigee adapter for Istio. +This workspace holds an apigee adapter for Istio. It can be tested by itself, but in order +to really use it you need a build of the Istio mixer that pulls it in. Instructions for that +are forthcoming. -## Installation +## Building -In order to use this, right now, you need a build of Istio \ No newline at end of file +You need Bazel, just like with Istio. + + make + +Builds everything, and + + make test + +Runs the tests. + +## Launching with the mixer + +Assuming that you have built a Mixer that includes it, you can launch the mixer as follows: + +First, edit testdata/global/adapters.yml to specify the organization and environment name for +your Apigee adapter to use. If you deployed "edgemicro-auth" to a production org, then +that is all you need. Otherwise, you can use "validationURL" to specify the base path +to another proxy. + +Now, launch the Mixer binary: + + $MIXER_HOME/bazel-bin/cmd/server/mixs --logtostderr --configStoreURL fs://$THIS_ROOT/testdata/configroot + +(In other words the "config store URL" needs to be the absolute path of the "testdata/configroot" +directory of this repo. If you get this wrong then the mixer will silently do nothing +and always return "OK!") + +Once that's running, here's a sample command: + + $MIXER_HOME/bazel-bin/cmd/client/mixc check -a target.serivce=fault.svc.cluster.local \ + --stringmap_attributes request.headers=apikey:SOME_VALID_API_KEY + +That will send a "check" RPC to the mixer, which will respond "OK" if and only if the +API key is valid. + +You can also run "bazel-bin/cmd/mockserver," which will implement the same API as +edgemicro-auth, but it will do it locally so that you can test easily. With the mock +server, "12345" is a valid API key.
diff --git a/adapter/BUILD b/adapter/BUILD index 0379f87..9e57dc2 100644 --- a/adapter/BUILD +++ b/adapter/BUILD
@@ -11,15 +11,18 @@ ], deps = [ "//adapter/config:go_default_library", + "//common:go_default_library", "//external:mixer_adapter", "@com_github_hashicorp_go_multierror//:go_default_library", ], ) go_test( - name = "small_tests", - size = "small", + name = "tests", + size = "medium", srcs = ["apigeeKeyChecker_test.go"], library = ":go_default_library", - deps = [], + deps = [ + "//mock:go_default_library", + ], )
diff --git a/adapter/apigee.go b/adapter/apigee.go index 8bf7f7d..c11818e 100644 --- a/adapter/apigee.go +++ b/adapter/apigee.go
@@ -14,7 +14,7 @@ limitations under the License. */ -package apigee +package adapter import ( "istio.io/mixer/pkg/adapter"
diff --git a/adapter/apigeeKeyChecker.go b/adapter/apigeeKeyChecker.go index 52d5010..6785f6f 100644 --- a/adapter/apigeeKeyChecker.go +++ b/adapter/apigeeKeyChecker.go
@@ -14,21 +14,24 @@ limitations under the License. */ -package apigee +package adapter import ( "bytes" "encoding/json" "fmt" "net/http" + "net/url" "github.com/apid/istioApigeeAdapter/adapter/config" + "github.com/apid/istioApigeeAdapter/common" "istio.io/mixer/pkg/adapter" ) const ( - checkName = "apigeeKeyChecker" - checkDesc = "Verify an API key from a parameter" + checkName = "apigeeKeyChecker" + checkDesc = "Verify an API key from a parameter" + verifyKeyPath = "/verifyApiKey" ) var checkConf = &config.VerifyKeyParams{} @@ -38,31 +41,51 @@ } type keyChecker struct { - keyParam string - organization string - environment string + env adapter.Env + checkURL string } -type APIKeyBody struct { - APIKey string `json:"apiKey"` -} - -func newKeyCheckBuilder() keyCheckBuilder { +func newKeyCheckBuilder() adapter.ListsBuilder { return keyCheckBuilder{ adapter.NewDefaultBuilder(checkName, checkDesc, checkConf), } } -func (keyCheckBuilder) NewListsAspect(env adapter.Env, c adapter.Config) (adapter.ListsAspect, error) { - return newKeyChecker(c.(*config.VerifyKeyParams)) +func (b keyCheckBuilder) ValidateConfig(c adapter.Config) (ce *adapter.ConfigErrors) { + cfg := c.(*config.VerifyKeyParams) + if cfg.Organization == "" { + ce = ce.Appendf("organization", "Organization parameter must be specified") + } + if cfg.Environment == "" { + ce = ce.Appendf("environment", "Environment parameter must be specified") + } + if cfg.VerificationURL != "" { + _, err := url.Parse(cfg.VerificationURL) + if err != nil { + ce = ce.Appendf("verificationURL", "Invalid verification URL: %s", err) + } + } + return } -func newKeyChecker(c *config.VerifyKeyParams) (*keyChecker, error) { - return &keyChecker{ - keyParam: c.KeyParameter, - organization: c.Organization, - environment: c.Environment, - }, nil +func (b keyCheckBuilder) NewListsAspect(env adapter.Env, c adapter.Config) (adapter.ListsAspect, error) { + cfg := c.(*config.VerifyKeyParams) + var basePath string + + if cfg.VerificationURL == "" { + basePath = fmt.Sprintf("https://%s-%s.apigee.net/edgemicro-auth", + cfg.Organization, cfg.Environment) + } else { + basePath = cfg.VerificationURL + } + + kc := &keyChecker{ + env: env, + checkURL: basePath + verifyKeyPath, + } + env.Logger().Infof("Created Apigee Key Checker to invoke \"%s\"", kc.checkURL) + + return kc, nil } func (l *keyChecker) Close() error { @@ -75,28 +98,22 @@ // Test command ./bazel-bin/cmd/client/mixc check -a target.service=f.default.svc.cluster.local --stringmap_attributes request.headers=x-api-key:1tu9pl04Srua2MtsAGtu6ViPxSYSSX2I func (l *keyChecker) CheckList(symbol string) (bool, error) { - fmt.Printf("*** Going to check \"%s\" against \"%s\"\n", symbol, l.organization) - - edge_url := "https://" + l.organization + "-" + l.environment + ".apigee.net/edgemicro-auth/verifyApiKey" - - fmt.Printf("*** edge_url \"%s\"\n", edge_url) - - return verifyApiKey(symbol, edge_url), nil -} - -func verifyApiKey(apiKey string, uri string) bool { - apiKeyBody := APIKeyBody{ - APIKey: apiKey, + apiKeyBody := common.VerifyAPIKeyRequest{ + Key: symbol, } - serializedBody, _ := json.Marshal(&apiKeyBody) - req, _ := http.NewRequest("POST", uri, bytes.NewBuffer(serializedBody)) - req.Header.Add("x-dna-api-key", apiKey) - req.Header.Add("content-type", "application/json") - client := &http.Client{} - resp, _ := client.Do(req) + requestBody, _ := json.Marshal(&apiKeyBody) + + resp, err := http.DefaultClient.Post(l.checkURL, "application/json", + bytes.NewBuffer(requestBody)) + if err != nil { + l.env.Logger().Errorf("Error contacting verification service: %s", err) + return false, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { - return false + return false, nil } else { - return true + return true, nil } }
diff --git a/adapter/apigeeKeyChecker_test.go b/adapter/apigeeKeyChecker_test.go index 6f4862d..51d99ce 100644 --- a/adapter/apigeeKeyChecker_test.go +++ b/adapter/apigeeKeyChecker_test.go
@@ -14,4 +14,124 @@ limitations under the License. */ -package apigee +package adapter + +import ( + "fmt" + "os" + "testing" + + "github.com/apid/istioApigeeAdapter/adapter/config" + "github.com/apid/istioApigeeAdapter/mock" + "istio.io/mixer/pkg/adapter" +) + +var mockServer *mock.MockServer +var mockEnv adapter.Env + +func TestMain(m *testing.M) { + var err error + mockEnv = mock.NewMockEnvironment() + mockServer, err = mock.StartMockServer(0) + if err != nil { + panic(err.Error()) + } + + result := m.Run() + mockServer.Stop() + os.Exit(result) +} + +func TestMissingOrg(t *testing.T) { + cfg := &config.VerifyKeyParams{ + Environment: "test", + } + builder := newKeyCheckBuilder() + ce := builder.ValidateConfig(cfg) + if ce == nil { + t.Fatal("Config should have returned an error") + } + fmt.Printf("Errors: %s\n", ce.Multi) +} + +func TestMissingEnv(t *testing.T) { + cfg := &config.VerifyKeyParams{ + Organization: "test", + } + builder := newKeyCheckBuilder() + ce := builder.ValidateConfig(cfg) + if ce == nil { + t.Fatal("Config should have returned an error") + } + fmt.Printf("Errors: %s\n", ce.Multi) +} + +func TestInvalidURL(t *testing.T) { + cfg := &config.VerifyKeyParams{ + Organization: "foo", + Environment: "test", + VerificationURL: ":", + } + builder := newKeyCheckBuilder() + ce := builder.ValidateConfig(cfg) + if ce == nil { + t.Fatal("Config should have returned an error") + } + fmt.Printf("Errors: %s\n", ce.Multi) +} + +func TestValidKey(t *testing.T) { + cfg := &config.VerifyKeyParams{ + Organization: "foo", + Environment: "test", + VerificationURL: "http://" + mockServer.Address(), + } + + builder := newKeyCheckBuilder() + ce := builder.ValidateConfig(cfg) + if ce != nil { + t.Fatalf("Error validating config: %s", ce) + } + + aspect, err := builder.NewListsAspect(mockEnv, cfg) + if err != nil { + t.Fatalf("Error creating aspect: %s", err) + } + defer aspect.Close() + + result, err := aspect.CheckList(mock.ValidAPIKey1) + if err != nil { + t.Fatalf("Error on list check: %s", err) + } + if !result { + t.Fatal("List check returned false") + } +} + +func TestInvalidKey(t *testing.T) { + cfg := &config.VerifyKeyParams{ + Organization: "foo", + Environment: "test", + VerificationURL: "http://" + mockServer.Address(), + } + + builder := newKeyCheckBuilder() + ce := builder.ValidateConfig(cfg) + if ce != nil { + t.Fatalf("Error validating config: %s", ce) + } + + aspect, err := builder.NewListsAspect(mockEnv, cfg) + if err != nil { + t.Fatalf("Error creating aspect: %s", err) + } + defer aspect.Close() + + result, err := aspect.CheckList("99999") + if err != nil { + t.Fatalf("Error on list check: %s", err) + } + if result { + t.Fatal("List check returned true and should have failed") + } +}
diff --git a/adapter/apigeeReport.go b/adapter/apigeeReport.go index d9f9325..2500b57 100644 --- a/adapter/apigeeReport.go +++ b/adapter/apigeeReport.go
@@ -14,7 +14,7 @@ limitations under the License. */ -package apigee +package adapter import ( "bytes"
diff --git a/adapter/config/config.proto b/adapter/config/config.proto index 48c7c6c..4d85bc7 100644 --- a/adapter/config/config.proto +++ b/adapter/config/config.proto
@@ -1,28 +1,32 @@ -// Copyright 2017 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. +/* +Copyright 2017 Google Inc. + +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. +*/ syntax = "proto3"; package config; message VerifyKeyParams { - // The name of the parameter where the API key is stored. - string key_parameter = 1; - - string organization = 2; - - string environment = 3; + // The name of the Apigee organization -- required + string organization = 1; + // The name of the Apigee environment -- required + string environment = 2; + // A URL to use to contact the verification service. Will be + // constructed from the organization and environment name if not + // specified, and assumes that the service runs at production "apigee.net". + string verificationURL = 3; } message ReportParams {
diff --git a/cmd/mockserver/BUILD b/cmd/mockserver/BUILD new file mode 100644 index 0000000..0b55061 --- /dev/null +++ b/cmd/mockserver/BUILD
@@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +load("@io_bazel_rules_go//go:def.bzl", "go_binary") + +go_binary( + name = "mockserver", + srcs = [ + "mockserver.go", + ], + deps = [ + "//mock:go_default_library", + ], +)
diff --git a/cmd/mockserver/mockserver.go b/cmd/mockserver/mockserver.go new file mode 100644 index 0000000..c0c13f6 --- /dev/null +++ b/cmd/mockserver/mockserver.go
@@ -0,0 +1,54 @@ +/* +Copyright 2017 Google Inc. + +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 main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/apid/istioApigeeAdapter/mock" +) + +func main() { + var port int + var help bool + + flag.IntVar(&port, "p", 0, "Port to listen on") + flag.BoolVar(&help, "h", false, "This help message right here") + flag.Parse() + if !flag.Parsed() || help { + flag.PrintDefaults() + os.Exit(2) + } + + server, err := mock.StartMockServer(port) + if err != nil { + fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err) + os.Exit(3) + } + fmt.Printf("Listening on %s\n", server.Address()) + + sc := make(chan os.Signal) + signal.Notify(sc, syscall.SIGTERM, syscall.SIGINT) + + sig := <-sc + fmt.Printf("Exiting on signal %s\n", sig) + server.Stop() +}
diff --git a/mock/BUILD b/mock/BUILD index 4bf127c..4893bb3 100644 --- a/mock/BUILD +++ b/mock/BUILD
@@ -6,14 +6,16 @@ name = "go_default_library", srcs = [ "keys.go", + "mockenv.go", "mockserver.go", ], deps = [ "//common:go_default_library", - "@com_github_julienschmidt_httprouter//:go_default_library", + "//external:mixer_adapter", "@com_github_SermoDigital_jose//crypto:go_default_library", "@com_github_SermoDigital_jose//jws:go_default_library", "@com_github_SermoDigital_jose//jwt:go_default_library", + "@com_github_julienschmidt_httprouter//:go_default_library", ], ) @@ -21,6 +23,7 @@ name = "small_tests", size = "small", srcs = [ + "mockenv_test.go", "mockserver_test.go", ], library = ":go_default_library",
diff --git a/mock/mockenv.go b/mock/mockenv.go new file mode 100644 index 0000000..1c9d176 --- /dev/null +++ b/mock/mockenv.go
@@ -0,0 +1,73 @@ +/* +Copyright 2017 Google Inc. + +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 mock + +import ( + "fmt" + + "istio.io/mixer/pkg/adapter" +) + +type mockEnvironment struct { + log *mockLogger +} + +/* +NewMockEnvironment returns an implementation of the Mixer adapter's "Env" class so that +we can test without launching the whole thing. +*/ +func NewMockEnvironment() adapter.Env { + return &mockEnvironment{ + log: &mockLogger{}, + } +} + +func (e *mockEnvironment) Logger() adapter.Logger { + return e.log +} + +func (e *mockEnvironment) ScheduleWork(fn adapter.WorkFunc) { + go func() { + fn() + }() +} + +func (e *mockEnvironment) ScheduleDaemon(fn adapter.DaemonFunc) { + go func() { + fn() + }() +} + +type mockLogger struct { +} + +func (l *mockLogger) VerbosityLevel(level adapter.VerbosityLevel) bool { + return true +} + +func (l *mockLogger) Infof(format string, args ...interface{}) { + fmt.Printf("INFO: "+format+"\n", args...) +} + +func (l *mockLogger) Warningf(format string, args ...interface{}) { + fmt.Printf("WARN: "+format+"\n", args...) +} + +func (l *mockLogger) Errorf(format string, args ...interface{}) error { + fmt.Printf("ERR: "+format+"\n", args...) + return nil +}
diff --git a/mock/mockenv_test.go b/mock/mockenv_test.go new file mode 100644 index 0000000..a312e1b --- /dev/null +++ b/mock/mockenv_test.go
@@ -0,0 +1,49 @@ +/* +Copyright 2017 Google Inc. + +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 mock + +import ( + "testing" +) + +func TestMockWork(t *testing.T) { + env := NewMockEnvironment() + + ch := make(chan bool) + env.Logger().Infof("Starting mock environment") + + env.ScheduleWork(func() { + env.Logger().Infof("Did the work %d times", 1) + ch <- true + }) + + <-ch +} + +func TestMockDaemon(t *testing.T) { + env := NewMockEnvironment() + + ch := make(chan bool) + env.Logger().Infof("Starting mock environment") + + env.ScheduleDaemon(func() { + env.Logger().Infof("Did the work %d times", 1) + ch <- true + }) + + <-ch +}
diff --git a/testdata/configroot/scopes/global/adapters.yml b/testdata/configroot/scopes/global/adapters.yml new file mode 100644 index 0000000..f4f2a68 --- /dev/null +++ b/testdata/configroot/scopes/global/adapters.yml
@@ -0,0 +1,25 @@ +subject: global +adapters: + - name: default + kind: quotas + impl: memQuota + params: + - name: default + impl: stdioLogger + params: + logStream: STDERR + - name: prometheus + kind: metrics + impl: prometheus + params: + - name: default + impl: denyChecker + - name: apigeeAPIKey + kind: lists + impl: apigeeKeyChecker + params: + organization: gregbrail + environment: prod + # Add verificationURL to use a different service or URL + #verificationURL: https://gregbrail-prod.apigee.net/edgemicro-auth +
diff --git a/testdata/configroot/scopes/global/descriptors.yml b/testdata/configroot/scopes/global/descriptors.yml new file mode 100644 index 0000000..341513e --- /dev/null +++ b/testdata/configroot/scopes/global/descriptors.yml
@@ -0,0 +1,191 @@ +subject: namespace:ns +revision: "2022" +manifests: + - name: kubernetes + revision: "1" + attributes: + source.ip: + valueType: IP_ADDRESS + source.labels: + valueType: STRING_MAP + source.name: + valueType: STRING + source.namespace: + valueType: STRING + source.service: + valueType: STRING + source.serviceAccount: + valueType: STRING + target.ip: + valueType: IP_ADDRESS + target.labels: + valueType: STRING_MAP + target.name: + valueType: STRING + target.namespace: + valueType: STRING + target.service: + valueType: STRING + target.serviceAccount: + valueType: STRING + - name: istio-proxy + revision: "1" + attributes: + origin.ip: + valueType: IP_ADDRESS + origin.uid: + valueType: STRING + origin.user: + valueType: STRING + request.headers: + valueType: STRING_MAP + request.id: + valueType: STRING + request.host: + valueType: STRING + request.method: + valueType: STRING + request.path: + valueType: STRING + request.reason: + valueType: STRING + request.referer: + valueType: STRING + request.scheme: + valueType: STRING + request.size: + valueType: INT64 + request.time: + valueType: TIMESTAMP + request.useragent: + valueType: STRING + response.code: + valueType: INT64 + response.duration: + valueType: DURATION + response.headers: + valueType: STRING_MAP + response.latency: + valueType: DURATION + response.size: + valueType: INT64 + response.time: + valueType: TIMESTAMP + source.uid: + valueType: STRING + target.uid: + valueType: STRING + # DEPRECATED, to be removed. Use request.useragent instead. + request.user-agent: + valueType: STRING +# Enums as struct fields can be symbolic names. +# However enums inside maps *cannot* be symbolic names. +metrics: + - name: request_count + kind: COUNTER + value: INT64 + description: request count by source, target, service, and code + labels: + source: 1 # STRING + target: 1 # STRING + service: 1 # STRING + method: 1 # STRING + response_code: 2 # INT64 + - name: request_duration + kind: DISTRIBUTION + value: DURATION + description: request duration by source, target, and service + buckets: + explicit_buckets: + bounds: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] + # Examples of other possible bucket configurations: + # linear_buckets: + # num_finite_buckets: 10 + # offset: 0.001 + # width: 0.1 + # exponential_buckets: + # num_finite_buckets: 15 + # scale: 0.001 + # growth_factor: 4 + labels: + source: 1 # STRING + target: 1 # STRING + service: 1 # STRING + method: 1 # STRING + response_code: 2 # INT64 + - name: request_size + kind: DISTRIBUTION + value: INT64 + description: request size by source, target, and service + buckets: + exponentialBuckets: + numFiniteBuckets: 8 + scale: 1 + growthFactor: 10 + # Examples of other possible bucket configurations: + # explicit_buckets: + # bounds: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] + # linear_buckets: + # num_finite_buckets: 10 + # offset: 0.001 + # width: 0.1 + labels: + source: 1 # STRING + target: 1 # STRING + service: 1 # STRING + method: 1 # STRING + response_code: 2 # INT64 + - name: response_size + kind: DISTRIBUTION + value: INT64 + description: response size by source, target, and service + buckets: + exponentialBuckets: + numFiniteBuckets: 8 + scale: 1 + growthFactor: 10 + # Examples of other possible bucket configurations: + # explicitBuckets: + # bounds: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] + # linearBuckets: + # numFiniteBuckets: 10 + # offset: 0.001 + # width: 0.1 + labels: + source: 1 # STRING + target: 1 # STRING + service: 1 # STRING + method: 1 # STRING + response_code: 2 # INT64 +quotas: + - name: RequestCount + rate_limit: true +logs: + - name: accesslog.common + display_name: Apache Common Log Format + payload_format: TEXT + log_template: '{{or (.originIp) "-"}} - {{or (.sourceUser) "-"}} [{{or (.timestamp.Format "02/Jan/2006:15:04:05 -0700") "-"}}] "{{or (.method) "-"}} {{or (.url) "-"}} {{or (.protocol) "-"}}" {{or (.responseCode) "-"}} {{or (.responseSize) "-"}}' + labels: + originIp: 6 # IP_ADDRESS + sourceUser: 1 # STRING + timestamp: 5 # TIMESTAMP + method: 1 # STRING + url: 1 # STRING + protocol: 1 # STRING + responseCode: 2 # INT64 + responseSize: 2 # INT64 + - name: accesslog.combined + display_name: Apache Combined Log Format + payload_format: TEXT + log_template: '{{or (.originIp) "-"}} - {{or (.sourceUser) "-"}} [{{or (.timestamp.Format "02/Jan/2006:15:04:05 -0700") "-"}}] "{{or (.method) "-"}} {{or (.url) "-"}} {{or (.protocol) "-"}}" {{or (.responseCode) "-"}} {{or (.responseSize) "-"}} {{or (.referer) "-"}} {{or (.userAgent) "-"}}' + labels: + originIp: 6 # IP_ADDRESS + sourceUser: 1 # STRING + timestamp: 5 # TIMESTAMP + method: 1 # STRING + url: 1 # STRING + protocol: 1 # STRING + responseCode: 2 # INT64 + responseSize: 2 # INT64 + referer: 1 # STRING + userAgent: 1 # STRING
diff --git a/testdata/configroot/scopes/global/subjects/global/rules.yml b/testdata/configroot/scopes/global/subjects/global/rules.yml new file mode 100644 index 0000000..f38981a --- /dev/null +++ b/testdata/configroot/scopes/global/subjects/global/rules.yml
@@ -0,0 +1,59 @@ +subject: namespace:ns +revision: "2022" +rules: +- selector: # must be empty for preprocessing adapters + aspects: + - kind: lists + adapter: apigeeAPIKey + params: + checkExpression: request.headers["apikey"] + - kind: quotas + params: + quotas: + - descriptorName: RequestCount + maxAmount: 5000 + expiration: 1s + - kind: metrics + adapter: prometheus + params: + metrics: + - descriptor_name: request_count + # we want to increment this counter by 1 for each unique (source, target, service, method, response_code) tuple + value: "1" + labels: + source: source.labels["app"] | "unknown" + target: target.service | "unknown" + service: target.labels["app"] | "unknown" + method: request.path | "unknown" + response_code: response.code | 200 + - descriptor_name: request_duration + value: response.latency | response.duration | "0ms" + labels: + source: source.labels["app"] | "unknown" + target: target.service | "unknown" + service: target.labels["app"] | "unknown" + method: request.path | "unknown" + response_code: response.code | 200 + - kind: access-logs + params: + logName: access_log + log: + descriptor_name: accesslog.common + template_expressions: + originIp: origin.ip + sourceUser: origin.user + timestamp: request.time + method: request.method + url: request.path + protocol: request.scheme + responseCode: response.code + responseSize: response.size + labels: + originIp: origin.ip + sourceUser: origin.user + timestamp: request.time + method: request.method + url: request.path + protocol: request.scheme + responseCode: response.code + responseSize: response.size