From 35b61b7c70b8c76617e6872fbc3724d99fb6aaed Mon Sep 17 00:00:00 2001 From: "GRECO, FRANK" Date: Wed, 4 Apr 2018 13:04:45 -0500 Subject: [PATCH] error code updates + docs --- hugo/content/docs/v2/configuration.md | 272 +++++++++++++++++++++++--- hugo/content/docs/v2/errorcodes.md | 175 +++++++++++++++-- hugo/content/docs/v2/plugins.md | 30 +++ hugo/content/docs/v2/security.md | 2 +- hugo/content/docs/v2/tools.md | 2 +- hugo/content/tutorial/_index.md | 2 +- pkg/errors/errors.go | 47 +++-- pkg/errors/errors_test.go | 8 + pkg/errors/utils_test.go | 4 +- pkg/flow/proxypass.go | 6 +- 10 files changed, 474 insertions(+), 74 deletions(-) create mode 100644 hugo/content/docs/v2/plugins.md diff --git a/hugo/content/docs/v2/configuration.md b/hugo/content/docs/v2/configuration.md index 63ed3a2..c091626 100644 --- a/hugo/content/docs/v2/configuration.md +++ b/hugo/content/docs/v2/configuration.md @@ -9,13 +9,95 @@ toc = true +++ ### Introduction -The goal of this section is to provide an introduction to each of Kanali's configurable resources. More in depth documentation for each resource can be found here. +The goal of this section is to provide an introduction to each of Kanali's configurable resources. More in depth documentation for each resource can be found [here](https://godoc.org/github.com/northwesternmutual/kanali/pkg/apis/kanali.io/v2).
### The `ApiProxy` Resource -This resource declaratively defines how your upstream services are +This resource declaratively defines how your upstream services are reached. There are 3 main sections that can be configured for this resource. + +source how your proxy will be accessible + +target how your upstream service will be reached + +plugins how your proxy will be secured + +Let's explore each in more detail. + +#### Source + +There are just 2 ways to configure a source, with or without a virtual host. If virtualHost is specified than, that proxy will only be discoverable if that host is being used. If virtualHost is not specified, than that proxy will be discoverable for all hosts. Here are examples of each. + +
+ +
+
+---
+apiVersion: kanali.io/v2
+kind: ApiProxy
+metadata:
+  name: example
+spec:
+  source:
+    path: /foo
+  target:
+    path: /
+    backend:
+      endpoint: https://foo.bar.com:8443
+
+ +
+
+---
+apiVersion: kanali.io/v2
+kind: ApiProxy
+metadata:
+  name: example
+spec:
+  source:
+    path: /bar
+    virtualHost: foo.bar.com
+  target:
+    path: /
+    backend:
+      endpoint: https://foo.bar.com:8443
+
+
+ +#### Target + +There are 3 main sections that can be configured for this resource. + +path upstream path + +backend backend type + +ssl tls configuration + +##### Path + +This fields specifies the upstream path that an upstream request will be proxied to. In the below example, if a request is made to /foo/baz, the upstream service will see /bar/baz. + +
+
+---
+apiVersion: kanali.io/v2
+kind: ApiProxy
+metadata:
+  name: example
+spec:
+  source:
+    path: /foo
+  target:
+    path: /bar
+    backend:
+      endpoint: https://foo.bar.com:8443
+
+ +##### Backend + +There are 4 different types of backends that you can configure. Toggle through each type below to learn more.
@@ -26,16 +108,24 @@ This resource declaratively defines how your upstream services are apiVersion: kanali.io/v2 kind: ApiProxy metadata: - name: example - namespace: default + name: example spec: - source: - path: /example - target: - service: - name: serviceName - port: 8080 - + source: + path: /example + target: + path: / + backend: + service: + name: serviceName + port: 8080 + +Statically defining an upstream Kubernetes service is the easiest way define an ApiProxy. Simply specify the name of your upstream Kubernetes service and the port your service is listening on. + +
+
+ +The namespace of this ApiProxy must match the namespace of the upstream service. +
@@ -44,19 +134,49 @@ apiVersion: kanali.io/v2
 kind: ApiProxy
 metadata:
  name: example
- namespace: default
 spec:
  source:
    path: /example
  target:
-   service:
-     port: 8080
-     labels:
-     - name: key
-       value: value
-     - name: deploy
-       header: x-foo-deployment
-    
+ path: / + backend: + service: + port: 8080 + labels: + - name: key + value: value + - name: deploy + header: x-foo-deployment + +Dynamically defining an upstream Kubernetes service provides an easy way to dynamically route traffic. To configure dynamic service discovery, simply specify the port your service is listening on and a set of labels. + +
+
+ +Labels work in a similar fashion to that of Kubernetes match labels. The name field of each label will be matched against a metadata label name on Kubernetes services. The second field of each label can either be a value or header. If value is specified, it corresponds directly to the Kubernetes service metadata label value. If header is specified, than the value of the Kubernetes service label will be matched against the value of the HTTP header specified by this label. Let's look at a quick example. + +
+
+ +Using the above ApiProxy as an example, If I make an request to /example and include the header x-foo-deployment: bar, Kanali will look for services in the default namespace that have at least the two following metadata labels. + +
+
+ +
+metadata:
+ labels:
+   key: value
+   deploy: bar
+
+ +If multiple upstream services are found, Kanali will use the first one and add the response header x-kanali-service-cardinality whose value matches the cardinality of the discovered upstream services to aid in troubleshooting. + +
+
+ +The namespace of this ApiProxy must match the namespace of the upstream service. +
@@ -65,14 +185,26 @@ apiVersion: kanali.io/v2
 kind: ApiProxy
 metadata:
  name: example
- namespace: default
 spec:
  source:
    path: /example
  target:
+   path: /
    backend:
-     endpoint: https://foo.bar.com:8443
-  
+ endpoint: https://foo.bar.com:8443 + +There may be times when you want to add API management to an upstream service that is not deployed to Kubernetes. To prevent you from deploying a different API management gateway to solve this specific use case, Kanali lets you proxy to arbitrary endpoints. To configure an arbitrary endpoint, just specify it as shown above. The value must have the following structure. + +
+
+ +<scheme>://<host> + +
+
+ +The scheme must either be http or https and if the host does not contain a port, 80 will be used as the default. +
@@ -81,18 +213,106 @@ apiVersion: kanali.io/v2
 kind: ApiProxy
 metadata:
  name: example
- namespace: default
 spec:
  source:
    path: /example
  target:
+   path: /
    backend:
      mock:
-       mockTargetName: mockTargetName
-  
+ mockTargetName: mockTargetName + +If this upstream type is used, Kanali will not make any request to any upstream service. Instead, it will return a preconfigured response. This response is defined in the MockTarget resource. This resource is explained in detail below. + +
+
+ +The namespace of this ApiProxy must match the namespace of the MockTarget resource. +
+##### SSL + +The presence of the ssl field specifies that tls will be used to secure the connection between Kanali and an upstream service. To configure this option, just specify the secret name containing the tls assets. An example is demonstrated below. + +
+
+---
+apiVersion: kanali.io/v2
+kind: ApiProxy
+metadata:
+  name: example
+spec:
+  source:
+    path: /foo
+  target:
+    path: /bar
+    backend:
+      endpoint: https://foo.bar.com:8443
+    ssl:
+      secretName: my-secret
+
+ +Let's assume that the specified secret above is structured like the example below. Note the presence of the kanali.io/enabled annotation. This annotation declares that Kanali is allowed to use this secret (this is due to Kubernetes RBAC limitations). + +Note the data fields present in this secret. If your upstream service wants to perform client side validation, the tls certificate/key pair as specified in the tls.crt and tls.key fields will be send to the server. + +
+
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: my-secret
+  annotations:
+    kanali.io/enabled: 'true'
+type: Opaque
+data:
+  tls.crt: 
+  tls.key: 
+  tls.ca: 
+
+ +If you want to customize the name of the data keys, you can specify your custom key via an annotation. For example, if you want to use the data key crt.pem instead of tls.crt, you would need to include the annotation kanali.io/cert: 'crt.pem'. A complete list of override annotations for the data fields are listed below. + + + + + + +
Data fieldAnnotation
tls.cakanali.io/ca: 'custom.ca.value'
tls.crtkanali.io/cert: 'custom.cert.value'
tls.keykanali.io/key: 'custom.key.value'
+ +#### Plugins + +Plugins enable the execution of encapsulated logic on a per proxy basis. Plugins are configured as a list of different plugins that you want executed for a specific ApiProxy. Each plugin in the list requires the name of the plugin and an optional config field specifying proxy level configuration items that will be passed to the plugin upon execution. + +For a complete list of available plugins and their corresponding documentation, visit the [documentation for plugins](/docs/v2/plugins). + +
+
+---
+apiVersion: kanali.io/v2
+kind: ApiProxy
+metadata:
+  name: example
+spec:
+  source:
+    path: /foo
+  target:
+    path: /bar
+    backend:
+      endpoint: https://foo.bar.com:8443
+  plugins:
+  - name: apikey
+    config:
+      bindingName: my-binding
+  - name: jwt
+    config:
+      audienceID: abc123
+ +
+ ### The `ApiKey` Resource ### The `ApiKeyBinding` Resource diff --git a/hugo/content/docs/v2/errorcodes.md b/hugo/content/docs/v2/errorcodes.md index 3eb9173..6358d32 100644 --- a/hugo/content/docs/v2/errorcodes.md +++ b/hugo/content/docs/v2/errorcodes.md @@ -16,29 +16,164 @@ If you're visiting this page, changes are that Kanali has given you a response s "status": 404, "message": "No ApiProxy resource was not found that matches the request.", "code": 0, - "details": "Visit https://kanali.io/docs/errorcodes for more details." + "details": "Visit https://kanali.io/docs/v2/errorcodes/#00 for more details." } ``` - As promised in that response, the table below contains detailed information about each unique error code. ### Details -| Code | Status | Description | -| ---- | ------ | ---------------- | -| 00 | 404 | *coming soon* | -| 01 | 500 | *coming soon* | -| 02 | 404 | *coming soon* | -| 03 | 500 | *coming soon* | -| 04 | 500 | *coming soon* | -| 05 | 500 | *coming soon* | -| 06 | 500 | *coming soon* | -| 07 | 500 | *coming soon* | -| 08 | 502 | *coming soon* | -| 09 | 500 | *coming soon* | -| 10 | 500 | *coming soon* | -| 11 | 500 | *coming soon* | -| 12 | 500 | *coming soon* | -| 13 | 403 | *coming soon* | -| 14 | 401 | *coming soon* | -| 15 | 429 | *coming soon* | +#### `00` + +**Status:** 404 + +**Message:** *No ApiProxy resource was not found that matches the request.* + +**Description:** Kanali was unable to match the given request to an ApiProxy resource at the time the request occurred. + +**Troubleshooting:** Verify the presence of the ApiProxy resource inside of the Kubernetes cluster that Kanali is deployed to. In addition, verify that the path in the request is prefixed with the source path of the ApiProxy resource. + +#### `01` + +**Status:** 500 + +**Message:** *An unknown error occurred.* + +**Description:** Something went wrong. However, no details are known at this time. + +**Troubleshooting:** Reach out to your Kanali administrator for further troubleshooting. + +#### `02` + +**Status:** 404 + +**Message:** *No MockTarget resource was not found that matches the request.* + +**Description:** Kanali was unable to find the MockTarget resource that was specified in the ApiProxy resource that matched this request. + +**Troubleshooting:** Verify that the name of the MockTarget that is configured in matching ApiProxy resource exists in the same namespace as the ApiProxy resource. + +#### `03` + +**Status:** 500 + +**Message:** *Could not open or load plugin.* + +**Description:** Kanali was unable to open or load one of the plugins specified in the ApiProxy resource that matched this request. + +**Troubleshooting:** Verify that there is a .so file for each corresponding plugin used by the matching ApiProxy. Look for these files in the location specified by the --plugins.location configuration flag. This value can be found in one of the very first logs that Kanali logs upon startup. + +#### `04` + +**Status:** 500 + +**Message:** *Could not lookup plugin symbol.* + +**Description:** Kanali attempted to execute one of the plugins configured on the ApiProxy resource that matched this request. However, when attempting to load one of the plugins, no exported variable, Plugin was found. + +**Troubleshooting:** Confirm that all of the plugins that the matching ApiProxy resource is using export the variable Plugin. If you find that one of the plugins does not do this, reach out to your Kanali administrator. + +#### `05` + +**Status:** 500 + +**Message:** *Plugin does not implement the correct interface.* + +**Description:** Kanali attempted to execute one of the plugins configured on the ApiProxy resource that matched this request. However, when attempting to execute the plugin, it was determined that the plugin did not implement the required interface. This interface can be found [here](https://godoc.org/github.com/northwesternmutual/kanali/pkg/plugin#Plugin). + +**Troubleshooting:** Confirm that all of the plugins that the matching ApiProxy resource is using implement the interface described above. If you find that one of the plugins do not implement this interface, reach out to your Kanali administrator. + +#### `06` + +**Status:** 500 + +**Message:** *Could not retrieve Kubernetes secret.* + +**Description:** Kanali attempted to retrieve the secret specified in the ssl field of the ApiProxy resource that matched this request. Due to one of a few reasons, there was an issue retrieving and/or using this secret. + +**Troubleshooting:** First, verify that there is a secret that has the same name as the one specified in the ApiProxy resource that matched this request. Note that this secret must also live in the same namespace as the ApiProxy resource. If the secret exists, verify that the kanali.io/enabled: 'true' annotation is present on the secret. Next, verify that the secret contains the correct data fields. Documentation about what fields are required can be found [here](/docs/v2/configuration/#ssl). If everything checks out, contact your Kanali administrator and let them know that Kanali may be having trouble connecting to the Kubernetes API server. + +#### `07` + +**Status:** 500 + +**Message:** *Could not create x509 key pair.* + +**Description:** Kanali found a valid secret as configured in the ApiProxy resource that matched this request. However, while attempting to parse the public/private key pair from the data in this secret, an error occurred. + +**Troubleshooting:** Validate that the public/private key pair that is specified in the above described secret is valid. One way to verify this is to use the following commands to generate md5 hashes. If the hashes are different, your key pair is not valid. + +
+openssl x509 -noout -modulus -in server.crt | openssl md5
+openssl rsa -noout -modulus -in server.key | openssl md5
+
+ +#### `08` + +**Status:** 502 + +**Message:** *Could not get a valid or any response from the upstream server.* + +**Description:** Kanali attempted to issue is request to an upstream service. However, something with this request went wrong. The issue could stem from the configuration of the ApiProxy resource matching this request, from the upstream service, from the networking in between Kanali and the upstream, or something else. + +**Troubleshooting:** First, verify that you are able to access the upstream service without Kanali (e.g. using curl from a busybox pod). If this is successful, make sure that the ApiProxy resource mathing this request is correctly configured so that it will route traffic to the expected upstream service. You might also ensure that any networking related items such as route tables, firewalls, etc. are correctly configured. + +#### `09` + +**Status:** 500 + +**Message:** *Could not retrieve Kubernetes services.* + +**Description:** Kanali determined that for this request, the upstream service is a Kubernetes service. However, when attempting to find out which service it might be, an error occurred. This does not imply that Kanali is unable to communicate with the Kubernetes API server. However, it does imply that Kanali has an issue. + +**Troubleshooting:** Try recycling Kanali's pod(s) and reach out to your Kanali administrator. + +#### `10` + +**Status:** 500 + +**Message:** *Could not retrieve any matching Kubernetes services.* + +**Description:** Kanali determined that for this request, the upstream service is a Kubernetes service. However, when attempting to find out which service it might be, none were found. + +**Troubleshooting:** Review the documentation for configuring the ApiProxy resource [here](/docs/v2/configuration). If static service discovery is being used, verify that a Kubernetes service in the same namespace as the matching ApiProxy resource exists. If dynamic service discovery is being used, verify that there exists a service in that same namespace that contains the dynamic labels. + +#### `11` + +**Status:** 500 + +**Message:** *Plugin threw a runtime error.* + +**Description:** Kanali attempted to execute one of the plugins configured on the ApiProxy resource that matched this request. However, when attempting to execute one of the plugins, a runtime error was thrown causing the plugin to crash. + +**Troubleshooting:** Contact your Kanali administrator as a bug in a plugin has been identified. + +#### `12` + +**Status:** 403 + +**Message:** *My lips are sealed.* + +**Description:** All I can say is that something went wrong. + +**Troubleshooting:** Like the message says, *My lips are sealed.*... + +#### `13` + +**Status:** 401 + +**Message:** *Api key is not authorized.* + +**Description:** Kanali was able to associate this request to a matching ApiProxy resource. In addition, the API key plugin was able to find an ApiKeyBinding resource that was configured in the ApiProxy resource. However, in the ApiKeyBinding resource, access it not granted to the API key that was used for the type of request performed. + +**Troubleshooting:** Verify that the API key used does not have permission to perform the request. If the ApiKeyBinding resource grants the API key used access to the specific request performed, contact your Kanali administrator for further troubleshooting. + +#### `14` + +**Status:** 429 + +**Message:** *The Api key you are using has exceeded its rate limit.* + +**Description:** Kanali was able to associate this request to a matching ApiProxy resource. In addition, the API key plugin was able to find an ApiKeyBinding resource that was configured in the ApiProxy resource. However, the API key used has exceeded its rate limit or quota policy. + +**Troubleshooting:** Verify that the API key used did indeed exceed its rate limit or quota. If the rate limit or quota was not exceeded, contact your Kanali administrator for further troubleshooting. \ No newline at end of file diff --git a/hugo/content/docs/v2/plugins.md b/hugo/content/docs/v2/plugins.md new file mode 100644 index 0000000..da05acd --- /dev/null +++ b/hugo/content/docs/v2/plugins.md @@ -0,0 +1,30 @@ ++++ +description = "Available Plugins" +title = "Plugins" +date = "2017-04-10T16:43:08+01:00" +draft = false +weight = 100 +bref="Available Plugins" +toc = true ++++ + +### API Key + +This plugin performs API key validation for all requests matching a certain ApiProxy. The plugin will ensure that the course and fine grained permissions that are specified in the given ApiKeyBinding resource are enforced. For details on how to configure the ApiKeyBinding resource, read the corresponding documentation [here](/docs/v2/configuration/#the-apikeybinding-resource). + +#### Configuration +
+ + +
FieldTypeDescription
bindingNamestringName of ApiKeyBinding resource, in the same namespace as the ApiProxy, to be used.
+ +#### Example +
+plugins:
+- name: apikey
+  config:
+    bindingName: my-binding
+ +### JWT + +> coming soon \ No newline at end of file diff --git a/hugo/content/docs/v2/security.md b/hugo/content/docs/v2/security.md index b7715eb..bfaa6c0 100644 --- a/hugo/content/docs/v2/security.md +++ b/hugo/content/docs/v2/security.md @@ -8,4 +8,4 @@ bref="Securing Kanali" toc = true +++ -### foo \ No newline at end of file +### Coming Soon \ No newline at end of file diff --git a/hugo/content/docs/v2/tools.md b/hugo/content/docs/v2/tools.md index 0a5cf2e..608d5f4 100644 --- a/hugo/content/docs/v2/tools.md +++ b/hugo/content/docs/v2/tools.md @@ -8,4 +8,4 @@ bref = "Developer tooling" toc = true +++ -### Coming soon \ No newline at end of file +### kanalictl \ No newline at end of file diff --git a/hugo/content/tutorial/_index.md b/hugo/content/tutorial/_index.md index a7a33b6..fefb9e0 100644 --- a/hugo/content/tutorial/_index.md +++ b/hugo/content/tutorial/_index.md @@ -14,7 +14,7 @@ type = "tutorial"