Merge branch 'master' of https://github.com/kubernetes/ingress-nginx into proxyssl

This commit is contained in:
Gabor Lekeny 2019-08-16 06:21:53 +02:00
commit 65b9e2c574
391 changed files with 23957 additions and 20447 deletions

26
.gitignore vendored
View file

@ -25,15 +25,6 @@
Session.vim
.netrwhist
# coverage artifacts
.coverprofile
/gover.coverprofile
e2e-tests
coverage.txt
test/e2e/e2e\.test
# mkdocs
site
@ -41,11 +32,18 @@ site
gh-pages
# Docker-based builds
/test/binaries
/.env
/.gocache/
/bin/
test/binaries
test/e2e-image/wait-for-nginx\.sh
# coverage artifacts
.coverprofile
gover.coverprofile
e2e-tests
coverage.txt
test/e2e/e2e\.test
.env
.gocache/
bin
test/e2e-image/wait-for-nginx.sh
.cache
cover.out

View file

@ -1,5 +1,13 @@
# Changelog
### 0.25.1
**Image:** `quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.1`
_Changes:_
- [X] [#4440](https://github.com/kubernetes/ingress-nginx/pull/4440) Fixes for CVE-2018-16843, CVE-2018-16844, CVE-2019-9511, CVE-2019-9513, and CVE-2019-9516
### 0.25.0
**Image:** `quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.25.0`
@ -25,7 +33,7 @@ _Non-functional improvements:_
_Changes:_
- [X] [#3506](https://github.com/kubernetes/ingress-nginx/pull/3506) Improve the external authorization concept from opt-in to secure-by-default
- [X] [#3506](https://github.com/kubernetes/ingress-nginx/pull/3506) Improve the external authorization concept from opt-in to secure-by-default
- [X] [#3802](https://github.com/kubernetes/ingress-nginx/pull/3802) Add a validating webhook for ingress sanity check
- [X] [#3803](https://github.com/kubernetes/ingress-nginx/pull/3803) use nkeys for counting lua table elements
- [X] [#3852](https://github.com/kubernetes/ingress-nginx/pull/3852) Enable arm again
@ -414,7 +422,7 @@ _Changes:_
- [X] [#3655](https://github.com/kubernetes/ingress-nginx/pull/3655) Remove flag sort-backends
- [X] [#3656](https://github.com/kubernetes/ingress-nginx/pull/3656) Change default value of flag for ssl chain completion
- [X] [#3660](https://github.com/kubernetes/ingress-nginx/pull/3660) Revert max-worker-connections default value
- [X] [#3664](https://github.com/kubernetes/ingress-nginx/pull/3664) Fix invalid validation creating prometheus valid host values
- [X] [#3664](https://github.com/kubernetes/ingress-nginx/pull/3664) Fix invalid validation creating prometheus valid host values
_Documentation:_

View file

@ -16,7 +16,7 @@
all: all-container
# Use the 0.0 tag for testing, it shouldn't clobber any release builds
TAG ?= 0.25.0
TAG ?= 0.25.1
REGISTRY ?= quay.io/kubernetes-ingress-controller
DOCKER ?= docker
SED_I ?= sed -i
@ -73,7 +73,7 @@ export E2E_CHECK_LEAKS
export SLOW_E2E_THRESHOLD
# Set default base image dynamically for each arch
BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.90
BASEIMAGE?=quay.io/kubernetes-ingress-controller/nginx-$(ARCH):0.91
ifeq ($(ARCH),arm)
QEMUARCH=arm

View file

@ -27,6 +27,9 @@ if [ -z "${PKG}" ]; then
exit 1
fi
export CGO_ENABLED=1
export GODEBUG=netdns=go+2
rm -rf coverage.txt
for d in $(go list "${PKG}/..." | grep -v vendor | grep -v '/test/e2e' | grep -v images); do
t=$(date +%s);

View file

@ -22,7 +22,6 @@ set -o errexit
set -o nounset
set -o pipefail
SKIP_MINIKUBE_START=${SKIP_MINIKUBE_START:-}
NAMESPACE="${NAMESPACE:-ingress-nginx}"
echo "NAMESPACE is set to ${NAMESPACE}"
@ -34,18 +33,15 @@ export REGISTRY=${REGISTRY:-ingress-controller}
DEV_IMAGE=${REGISTRY}/nginx-ingress-controller:${TAG}
if [ -z "${SKIP_MINIKUBE_START}" ]; then
test "$(minikube status | grep -c Running) -ge 2 && $(minikube status | grep -q 'Correctly Configured')" || minikube start \
--extra-config=kubelet.sync-frequency=1s \
--extra-config=apiserver.authorization-mode=RBAC
test $(minikube status | grep -c Running) -ge 2 && $(minikube status | grep -q 'Correctly Configured') || minikube start \
--extra-config=kubelet.sync-frequency=1s \
--extra-config=apiserver.authorization-mode=RBAC
eval $(minikube docker-env --shell bash)
fi
eval $(minikube docker-env --shell bash)
echo "[dev-env] building container"
make build container
docker save "${DEV_IMAGE}" | (eval $(minikube docker-env --shell bash) && docker load) || true
docker tag "${REGISTRY}/nginx-ingress-controller-${ARCH}:${TAG}" ${DEV_IMAGE}
# kubectl >= 1.14 includes Kustomize via "apply -k". Makes it easier to use on Linux as well, assuming kubectl installed
KUBE_CLIENT_VERSION=$(kubectl version --client --short | awk '{print $3}' | cut -d. -f2) || true
@ -60,6 +56,8 @@ if ! kubectl get namespace "${NAMESPACE}"; then
kubectl create namespace "${NAMESPACE}"
fi
kubectl get deploy nginx-ingress-controller -n ${NAMESPACE} && kubectl delete deploy nginx-ingress-controller -n ${NAMESPACE}
ROOT=./deploy/minikube
if [[ ${KUBE_CLIENT_VERSION} -lt 14 ]]; then
@ -71,8 +69,7 @@ if [[ ${KUBE_CLIENT_VERSION} -lt 14 ]]; then
echo "[dev-env] deploying NGINX Ingress controller in namespace $NAMESPACE"
kustomize build $ROOT | kubectl apply -f -
else
sed -i "\\|^namespace:|c \\namespace: ${NAMESPACE}" "${ROOT}/kustomization.yaml"
sed -i "\\|^- name: quay.io|c \\- name: quay.io/kubernetes-ingress-controller/nginx-ingress-controller=${DEV_IMAGE}" "${ROOT}/kustomization.yaml"
sed -i -e "s|^namespace: .*|namespace: ${NAMESPACE}|g" "${ROOT}/kustomization.yaml"
echo "[dev-env] deploying NGINX Ingress controller in namespace $NAMESPACE"
kubectl apply -k "${ROOT}"

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM alpine:3.9
FROM alpine:3.10
RUN apk update && apk add --no-cache \
bash \

View file

@ -63,7 +63,8 @@ kubectl create clusterrolebinding permissive-binding \
--user=kubelet \
--serviceaccount=default:ingress-nginx-e2e || true
until kubectl get secret | grep -q ^ingress-nginx-e2e-token; do \
echo -e "${BGREEN}Waiting service account...${NC}"; \
until kubectl get secret | grep -q -e ^ingress-nginx-e2e-token; do \
echo -e "waiting for api token"; \
sleep 3; \
done

View file

@ -30,6 +30,7 @@ declare -a mandatory
mandatory=(
IMAGE
ARCH
TAG
)
missing=false
@ -56,12 +57,12 @@ trap cleanup EXIT
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
# the ingress controller needs this two variables. To avoid the
# the ingress controller needs this two variables. To avoid the
# creation of any object in the cluster we use invalid names.
POD_NAMESPACE="invalid-namespace"
POD_NAME="invalid-namespace"
export TAG=local
export TAG
export IMAGE
if [[ "${ARCH}" != "amd64" ]]; then
@ -69,8 +70,14 @@ if [[ "${ARCH}" != "amd64" ]]; then
make -C "${KUBE_ROOT}" register-qemu
fi
echo -e "${BGREEN}Building ingress controller image${NC}"
make -C "${KUBE_ROOT}" build "sub-container-${ARCH}"
USE_EXISTING_IMAGE=${USE_EXISTING_IMAGE:-false}
if [[ "${USE_EXISTING_IMAGE}" == "true" ]]; then
echo -e "${BGREEN}Downloading ingress controller image${NC}"
docker pull "${IMAGE}-${ARCH}:${TAG}"
else
echo -e "${BGREEN}Building ingress controller image${NC}"
make -C "${KUBE_ROOT}" build "sub-container-${ARCH}"
fi
CONTEXT=$(kubectl config current-context)
@ -103,7 +110,7 @@ docker run \
-v "${SSL_VOLUME}:/etc/ingress-controller/ssl/" \
-v "${HOME}/.kube:${HOME}/.kube:ro" \
${MINIKUBE_VOLUME} \
"${IMAGE}:${TAG}" /nginx-ingress-controller \
"${IMAGE}-${ARCH}:${TAG}" /nginx-ingress-controller \
--update-status=false \
--v=2 \
--apiserver-host=http://0.0.0.0:8001 \

View file

@ -29,4 +29,5 @@ resty \
--shdict "certificate_data 16M" \
--shdict "balancer_ewma 1M" \
--shdict "balancer_ewma_last_touched_at 1M" \
--shdict "balancer_ewma_locks 512k" \
./rootfs/etc/nginx/lua/test/run.lua ${BUSTED_ARGS} ./rootfs/etc/nginx/lua/test/

View file

@ -28,5 +28,9 @@ if [ -z "${PKG}" ]; then
exit 1
fi
go test -v -race -tags "cgo" \
# enabled to use host dns resolver
export CGO_ENABLED=1
export GODEBUG=netdns=go+2
go test -v -race \
$(go list "${PKG}/..." | grep -v vendor | grep -v '/test/e2e' | grep -v images | grep -v "docs/examples")

View file

@ -141,7 +141,7 @@ extension for this to succeed.`)
`Customized address to set as the load-balancer status of Ingress objects this controller satisfies.
Requires the update-status parameter.`)
enableDynamicCertificates = flags.Bool("enable-dynamic-certificates", true,
_ = flags.Bool("enable-dynamic-certificates", true,
`Dynamically update SSL certificates instead of reloading NGINX. Feature backed by OpenResty Lua libraries.`)
enableMetrics = flags.Bool("enable-metrics", true,
@ -171,6 +171,8 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
flags.MarkDeprecated("status-port", `The status port is a unix socket now.`)
flags.MarkDeprecated("force-namespace-isolation", `This flag doesn't do anything.`)
flags.MarkDeprecated("enable-dynamic-certificates", `Only dynamic mode is supported`)
flag.Set("logtostderr", "true")
flags.AddGoFlagSet(flag.CommandLine)
@ -232,7 +234,6 @@ Takes the form "<host>:port". If not provided, no admission controller is starte
}
ngx_config.EnableSSLChainCompletion = *enableSSLChainCompletion
ngx_config.EnableDynamicCertificates = *enableDynamicCertificates
config := &controller.Configuration{
APIServerHost: *apiserverHost,

View file

@ -39,7 +39,6 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/controller"
"k8s.io/ingress-nginx/internal/ingress/metric"
"k8s.io/ingress-nginx/internal/k8s"
@ -63,13 +62,6 @@ func main() {
klog.Fatal(err)
}
nginxVersion()
fs, err := file.NewLocalFS()
if err != nil {
klog.Fatal(err)
}
kubeClient, err := createApiserverClient(conf.APIServerHost, conf.KubeConfigFile)
if err != nil {
handleFatalInitError(err)
@ -98,8 +90,8 @@ func main() {
}
}
conf.FakeCertificate = ssl.GetFakeSSLCert(fs)
klog.Infof("Created fake certificate with PemFileName: %v", conf.FakeCertificate.PemFileName)
conf.FakeCertificate = ssl.GetFakeSSLCert()
klog.Infof("SSL fake certificate created %v", conf.FakeCertificate.PemFileName)
k8s.IsNetworkingIngressAvailable = k8s.NetworkingIngressAvailable(kubeClient)
if !k8s.IsNetworkingIngressAvailable {
@ -125,7 +117,7 @@ func main() {
}
mc.Start()
ngx := controller.NewNGINXController(conf, mc, fs)
ngx := controller.NewNGINXController(conf, mc)
go handleSigterm(ngx, func(code int) {
os.Exit(code)
})

View file

@ -19,6 +19,7 @@ package main
import (
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"time"
@ -28,8 +29,8 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/controller"
"k8s.io/ingress-nginx/internal/nginx"
)
func TestCreateApiserverClient(t *testing.T) {
@ -39,6 +40,15 @@ func TestCreateApiserverClient(t *testing.T) {
}
}
func init() {
// the default value of nginx.TemplatePath assumes the template exists in
// the root filesystem and not in the rootfs directory
path, err := filepath.Abs(filepath.Join("../../rootfs/", nginx.TemplatePath))
if err == nil {
nginx.TemplatePath = path
}
}
func TestHandleSigterm(t *testing.T) {
clientSet := fake.NewSimpleClientset()
@ -77,12 +87,7 @@ func TestHandleSigterm(t *testing.T) {
}
conf.Client = clientSet
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
ngx := controller.NewNGINXController(conf, nil, fs)
ngx := controller.NewNGINXController(conf, nil)
go handleSigterm(ngx, func(code int) {
if code != 1 {

View file

@ -1,37 +0,0 @@
/*
Copyright 2018 The Kubernetes 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.
*/
package main
import (
"os"
"os/exec"
"k8s.io/klog"
)
func nginxVersion() {
flag := "-v"
if klog.V(2) {
flag = "-V"
}
cmd := exec.Command("nginx", flag)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}

View file

@ -95,6 +95,21 @@ $ export REGISTRY=<your-docker-registry>
To find the registry simply run: `docker system info | grep Registry`
### Building the e2e test image
The e2e test image can also be built through the Makefile.
```console
$ make e2e-test-image
```
You can then make this image available on your minikube host by exporting the image and loading it with the minikube docker context:
```console
$ docker save nginx-ingress-controller:e2e | (eval $(minikube docker-env) && docker load)
```
### Nginx Controller
Build a raw server binary
@ -137,6 +152,8 @@ $ cd $GOPATH/src/k8s.io/ingress-nginx
$ make e2e-test
```
NOTE: if your e2e pod keeps hanging in an ImagePullBackoff, make sure you've made your e2e nginx-ingress-controller image available to minikube as explained in [Building the e2e test image](./Building the e2e test image)
To run unit-tests for lua code locally, run:
```console

View file

@ -0,0 +1,63 @@
---
title: Remove static SSL configuration mode
authors:
- "@aledbf"
reviewers:
- "@ElvinEfendi"
approvers:
- "@ElvinEfendi"
editor: TBD
creation-date: 2019-07-24
last-updated: 2019-07-24
status: implementable
see-also:
replaces:
superseded-by:
---
# Remove static SSL configuration mode
## Table of Contents
<!-- toc -->
- [Summary](#summary)
- [Motivation](#motivation)
- [Goals](#goals)
- [Non-Goals](#non-goals)
- [Proposal](#proposal)
- [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)
- [Drawbacks](#drawbacks)
- [Alternatives](#alternatives)
<!-- /toc -->
## Summary
Since release [0.19.0](https://github.com/kubernetes/ingress-nginx/releases/tag/nginx-0.19.0) is possible to configure SSL certificates without the need of NGINX reloads (thanks to lua) and after release [0.24.0](https://github.com/kubernetes/ingress-nginx/releases/tag/nginx-0.19.0) the default enabled mode is dynamic.
## Motivation
The static configuration implies reloads, something that affects the majority of the users.
### Goals
- Deprecation of the flag `--enable-dynamic-certificates`.
- Cleanup of the codebase.
### Non-Goals
- Features related to certificate authentication are not changed in any way.
## Proposal
- Remove static SSL configuration
### Implementation Details/Notes/Constraints
- Deprecate the flag Move the directives `ssl_certificate` and `ssl_certificate_key` from each server block to the `http` section. These settings are required to avoid NGINX errors in the logs.
- Remove any action of the flag `--enable-dynamic-certificates`
## Drawbacks
## Alternatives
Keep both implementations

View file

@ -0,0 +1,51 @@
---
title: Availability zone aware routing
authors:
- "@ElvinEfendi"
reviewers:
- "@aledbf"
approvers:
- "@aledbf"
editor: TBD
creation-date: 2019-08-15
last-updated: 2019-08-15
status: provisional
---
# Availability zone aware routing
## Table of Contents
<!-- toc -->
- [Summary](#summary)
- [Motivation](#motivation)
- [Goals](#goals)
- [Non-Goals](#non-goals)
<!-- /toc -->
## Summary
Teach ingress-nginx about availability zones where endpoints are running in. This way ingress-nginx pod will do its best to proxy to zone-local endpoint.
## Motivation
When users run their services across multiple availability zones they usually pay for egress traffic between zones. Providers such as GCP, AWS charges money for that.
ingress-nginx when picking an endpoint to route request to does not consider whether the endpoint is in different zone or the same one. That means it's at least equally likely
that it will pick an endpoint from another zone and proxy the request to it. In this situation response from the endpoint to ingress-nginx pod is considered as
inter zone traffic and costs money.
At the time of this writing GCP charges $0.01 per GB of inter zone egress traffic according to https://cloud.google.com/compute/network-pricing.
According to https://datapath.io/resources/blog/what-are-aws-data-transfer-costs-and-how-to-minimize-them/ AWS also charges the same amount of money sa GCP for cross zone, egress traffic.
This can be a lot of money depending on once's traffic. By teaching ingress-nginx about zones we can eliminate or at least decrease this cost.
Arguably inter-zone network latency should also be better than cross zone.
### Goals
* Given a regional cluster running ingress-nginx, ingress-nginx should do best effort to pick zone-local endpoint when proxying
### Non-Goals
* -

View file

@ -0,0 +1,28 @@
# Kubernetes Enhancement Proposals (KEPs)
A Kubernetes Enhancement Proposal (KEP) is a way to propose, communicate and coordinate on new efforts for the Kubernetes project. For this reason, the `ingress-nginx` project is adopting it.
## Quick start for the KEP process
Follow the process outlined in the [KEP template](YYYYMMDD-kep-template.md)
### Do I have to use the KEP process?
No... but we hope that you will.
Over time having a rich set of KEPs in one place will make it easier for people to track what is going on in the community and find a structured historic record.
KEPs are only required when the changes are wide ranging and impact most of the project.
### Why would I want to use the KEP process?
Our aim with KEPs is to clearly communicate new efforts to the Kubernetes contributor community.
As such, we want to build a well curated set of clear proposals in a common format with useful metadata.
Benefits to KEP users (in the limit):
* Exposure on a kubernetes blessed web site that is findable via web search engines.
* Cross indexing of KEPs so that users can find connections and the current status of any KEP.
* A clear process with approvers and reviewers for making decisions.
This will lead to more structured decisions that stick as there is a discoverable record around the decisions.
We are inspired by IETF RFCs, Python PEPs, and Rust RFCs.

View file

@ -0,0 +1,182 @@
---
title: KEP Template
authors:
- "@janedoe"
reviewers:
- TBD
- "@alicedoe"
approvers:
- TBD
- "@oscardoe"
editor: TBD
creation-date: yyyy-mm-dd
last-updated: yyyy-mm-dd
status: provisional|implementable|implemented|deferred|rejected|withdrawn|replaced
see-also:
- "/docs/enhancements/20190101-we-heard-you-like-keps.md"
- "/docs/enhancements/20190102-everyone-gets-a-kep.md"
replaces:
- "/docs/enhancements/20181231-replaced-kep.md"
superseded-by:
- "/docs/enhancements/20190104-superceding-kep.md"
---
# Title
This is the title of the KEP.
Keep it simple and descriptive.
A good title can help communicate what the KEP is and should be considered as part of any review.
The title should be lowercased and spaces/punctuation should be replaced with `-`.
To get started with this template:
1. **Make a copy of this template.**
Create a copy of this template and name it `YYYYMMDD-my-title.md`, where `YYYYMMDD` is the date the KEP was first drafted.
1. **Fill out the "overview" sections.**
This includes the Summary and Motivation sections.
These should be easy if you've preflighted the idea of the KEP in an issue.
1. **Create a PR.**
Assign it to folks that are sponsoring this process.
1. **Create an issue**
When filing an enhancement tracking issue, please ensure to complete all fields in the template.
1. **Merge early.**
Avoid getting hung up on specific details and instead aim to get the goal of the KEP merged quickly.
The best way to do this is to just start with the "Overview" sections and fill out details incrementally in follow on PRs.
View anything marked as a `provisional` as a working document and subject to change.
Aim for single topic PRs to keep discussions focused.
If you disagree with what is already in a document, open a new PR with suggested changes.
The canonical place for the latest set of instructions (and the likely source of this file) is [here](/keps/YYYYMMDD-kep-template.md).
The `Metadata` section above is intended to support the creation of tooling around the KEP process.
This will be a YAML section that is fenced as a code block.
See the KEP process for details on each of these items.
## Table of Contents
A table of contents is helpful for quickly jumping to sections of a KEP and for highlighting any additional information provided beyond the standard KEP template.
Ensure the TOC is wrapped with <code>&lt;!-- toc --&rt;&lt;!-- /toc --&rt;</code> tags, and then generate with `hack/update-toc.sh`.
<!-- toc -->
- [Summary](#summary)
- [Motivation](#motivation)
- [Goals](#goals)
- [Non-Goals](#non-goals)
- [Proposal](#proposal)
- [User Stories [optional]](#user-stories-optional)
- [Story 1](#story-1)
- [Story 2](#story-2)
- [Implementation Details/Notes/Constraints [optional]](#implementation-detailsnotesconstraints-optional)
- [Risks and Mitigations](#risks-and-mitigations)
- [Design Details](#design-details)
- [Test Plan](#test-plan)
- [Removing a deprecated flag](#removing-a-deprecated-flag)
- [Implementation History](#implementation-history)
- [Drawbacks [optional]](#drawbacks-optional)
- [Alternatives [optional]](#alternatives-optional)
<!-- /toc -->
## Summary
The `Summary` section is incredibly important for producing high quality user-focused documentation such as release notes or a development roadmap.
It should be possible to collect this information before implementation begins in order to avoid requiring implementors to split their attention between writing release notes and implementing the feature itself.
A good summary is probably at least a paragraph in length.
## Motivation
This section is for explicitly listing the motivation, goals and non-goals of this KEP.
Describe why the change is important and the benefits to users.
The motivation section can optionally provide links to [experience reports][] to demonstrate the interest in a KEP within the wider Kubernetes community.
[experience reports]: https://github.com/golang/go/wiki/ExperienceReports
### Goals
List the specific goals of the KEP.
How will we know that this has succeeded?
### Non-Goals
What is out of scope for this KEP?
Listing non-goals helps to focus discussion and make progress.
## Proposal
This is where we get down to the nitty gritty of what the proposal actually is.
### User Stories [optional]
Detail the things that people will be able to do if this KEP is implemented.
Include as much detail as possible so that people can understand the "how" of the system.
The goal here is to make this feel real for users without getting bogged down.
#### Story 1
#### Story 2
### Implementation Details/Notes/Constraints [optional]
What are the caveats to the implementation?
What are some important details that didn't come across above.
Go in to as much detail as necessary here.
This might be a good place to talk about core concepts and how they releate.
### Risks and Mitigations
What are the risks of this proposal and how do we mitigate.
Think broadly.
For example, consider both security and how this will impact the larger kubernetes ecosystem.
How will security be reviewed and by whom?
How will UX be reviewed and by whom?
Consider including folks that also work outside project.
## Design Details
### Test Plan
**Note:** *Section not required until targeted at a release.*
Consider the following in developing a test plan for this enhancement:
- Will there be e2e and integration tests, in addition to unit tests?
- How will it be tested in isolation vs with other components?
No need to outline all of the test cases, just the general strategy.
Anything that would count as tricky in the implementation and anything particularly challenging to test should be called out.
All code is expected to have adequate tests (eventually with coverage expectations).
Please adhere to the [Kubernetes testing guidelines][testing-guidelines] when drafting this test plan.
[testing-guidelines]: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
#### Removing a deprecated flag
- Announce deprecation and support policy of the existing flag
- Two versions passed since introducing the functionality which deprecates the flag (to address version skew)
- Address feedback on usage/changed behavior, provided on GitHub issues
- Deprecate the flag
## Implementation History
Major milestones in the life cycle of a KEP should be tracked in `Implementation History`.
Major milestones might include
- the `Summary` and `Motivation` sections being merged signaling acceptance
- the `Proposal` section being merged signaling agreement on a proposed design
- the date implementation started
- the first Kubernetes release where an initial version of the KEP was available
- the version of Kubernetes where the KEP graduated to general availability
- when the KEP was retired or superseded
## Drawbacks [optional]
Why should this KEP _not_ be implemented.
## Alternatives [optional]
Similar to the `Drawbacks` section the `Alternatives` section is used to highlight and record other possible approaches to delivering the value proposed by a KEP.

View file

@ -31,7 +31,7 @@ metadata:
### Example: OAuth2 Proxy + Kubernetes-Dashboard
This example will show you how to deploy [`oauth2_proxy`](https://github.com/bitly/oauth2_proxy)
This example will show you how to deploy [`oauth2_proxy`](https://github.com/pusher/oauth2_proxy)
into a Kubernetes cluster and use it to protect the Kubernetes Dashboard using github as oAuth2 provider
#### Prepare
@ -55,7 +55,7 @@ kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addon
- OAUTH2_PROXY_CLIENT_ID with the github `<Client ID>`
- OAUTH2_PROXY_CLIENT_SECRET with the github `<Client Secret>`
- OAUTH2_PROXY_COOKIE_SECRET with value of `python -c 'import os,base64; print base64.b64encode(os.urandom(16))'`
- OAUTH2_PROXY_COOKIE_SECRET with value of `python -c 'import os,base64; print base64.b64encode(os.urandom(16))'`
4. Customize the contents of the file dashboard-ingress.yaml:

View file

@ -31,7 +31,7 @@ spec:
# docker run -ti --rm python:3-alpine python -c 'import secrets,base64; print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))));'
- name: OAUTH2_PROXY_COOKIE_SECRET
value: SECRET
image: docker.io/colemickens/oauth2_proxy:latest
image: quay.io/pusher/oauth2_proxy:latest
imagePullPolicy: Always
name: oauth2-proxy
ports:

View file

@ -0,0 +1,23 @@
# Pod Security Policy (PSP)
In most clusters today, by default, all resources (e.g. Deployments and ReplicatSets)
have permissions to create pods.
Kubernetes however provides a more fine-grained authorization policy called
[Pod Security Policy (PSP)](https://kubernetes.io/docs/concepts/policy/pod-security-policy/).
PSP allows the cluster owner to define the permission of each object, for example creating a pod.
If you have PSP enabled on the cluster, and you deploy ingress-nginx,
you will need to provide the Deployment with the permissions to create pods.
Before applying any objects, first apply the PSP permissions by running:
```console
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/docs/examples/psp/psp.yaml
```
Now that the pod security policy is applied, we can continue as usual by applying the
[mandatory.yaml](https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml)
according to the [Installation Guide](../../deploy/index.md).
Note: PSP permissions must be granted before to the creation of the Deployment and the ReplicaSet.
If the Deployment or ReplicaSet already exist, they will receive the PSP permissions
only after deleting them and reapplying mandatory.yaml.

View file

@ -0,0 +1,87 @@
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
annotations:
# Assumes apparmor available
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default'
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default'
name: ingress-nginx
spec:
allowedCapabilities:
- NET_BIND_SERVICE
allowPrivilegeEscalation: true
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
hostIPC: false
hostNetwork: false
hostPID: false
hostPorts:
- min: 80
max: 65535
privileged: false
readOnlyRootFilesystem: false
runAsUser:
rule: 'MustRunAsNonRoot'
ranges:
- min: 33
max: 65535
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
volumes:
- 'configMap'
- 'downwardAPI'
- 'emptyDir'
- 'projected'
- 'secret'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-psp
namespace: ingress-nginx
rules:
- apiGroups:
- policy
resourceNames:
- ingress-nginx
resources:
- podsecuritypolicies
verbs:
- use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-psp
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-psp
subjects:
- kind: ServiceAccount
name: default
- kind: ServiceAccount
name: nginx-ingress-serviceaccount

View file

@ -120,7 +120,7 @@ $ kubectl ingress-nginx backends -n ingress-nginx
"secureCACert": {
"secret": "",
"caFilename": "",
"pemSha": ""
"caSha": ""
},
"sslPassthrough": false,
"endpoints": [

View file

@ -44,7 +44,6 @@ They are set in the container spec of the `nginx-ingress-controller` Deployment
| `--version` | Show release information about the NGINX Ingress controller and exit. |
| `--vmodule moduleSpec` | comma-separated list of pattern=N settings for file-filtered logging |
| `--watch-namespace string` | Namespace the controller watches for updates to Kubernetes objects. This includes Ingresses, Services and all configuration resources. All namespaces are watched if this parameter is left empty. |
| `--disable-catch-all` | Disable support for catch-all Ingresses. |
|`--validating-webhook`|The address to start an admission controller on|
|`--validating-webhook-certificate`|The certificate the webhook is using for its TLS handling|
|`--validating-webhook-key`|The key the webhook is using for its TLS handling|

View file

@ -0,0 +1,117 @@
# Exposing FastCGI Servers
> **FastCGI** is a [binary protocol](https://en.wikipedia.org/wiki/Binary_protocol "Binary protocol") for interfacing interactive programs with a [web server](https://en.wikipedia.org/wiki/Web_server "Web server"). [...] (It's) aim is to reduce the overhead related to interfacing between web server and CGI programs, allowing a server to handle more web page requests per unit of time.
>
> &mdash; Wikipedia
The _ingress-nginx_ ingress controller can be used to directly expose [FastCGI](https://en.wikipedia.org/wiki/FastCGI) servers. Enabling FastCGI in your Ingress only requires setting the _backend-protocol_ annotation to `FCGI`, and with a couple more annotations you can customize the way _ingress-nginx_ handles the communication with your FastCGI _server_.
## Example Objects to Expose a FastCGI Pod
The _Pod_ example object below exposes port `9000`, which is the conventional FastCGI port.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-app
labels:
app: example-app
spec:
containers:
- name: example-app
image: example-app:1.0
ports:
- containerPort: 9000
name: fastcgi
```
The _Service_ object example below matches port `9000` from the _Pod_ object above.
```yaml
apiVersion: v1
kind: Service
metadata:
name: example-service
spec:
selector:
app: example-app
ports:
- port: 9000
targetPort: 9000
name: fastcgi
```
And the _Ingress_ and _ConfigMap_ objects below demonstrates the supported _FastCGI_ specific annotations (NGINX actually has 50 FastCGI directives, all of which have not been exposed in the ingress yet), and matches the service `example-service`, and the port named `fastcgi` from above. The _ConfigMap_ **must** be created first for the _Ingress Controller_ to be able to find it when the _Ingress_ object is created, otherwise you will need to restart the _Ingress Controller_ pods.
```yaml
# The ConfigMap MUST be created first for the ingress controller to be able to
# find it when the Ingress object is created.
apiVersion: v1
kind: ConfigMap
metadata:
name: example-cm
data:
SCRIPT_FILENAME: "/example/index.php"
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "FCGI"
nginx.ingress.kubernetes.io/fastcgi-index: "index.php"
nginx.ingress.kubernetes.io/fastcgi-params-configmap: "example-cm"
name: example-app
spec:
rules:
- host: app.example.com
http:
paths:
- backend:
serviceName: example-service
servicePort: fastcgi
```
## The FastCGI Ingress Annotations
### The `nginx.ingress.kubernetes.io/backend-protocol` Annotation
To enable FastCGI, the `backend-protocol` annotation needs to be set to `FCGI`, which overrides the default `HTTP` value.
> `nginx.ingress.kubernetes.io/backend-protocol: "FCGI"`
This enables the _FastCGI_ mode for the whole _Ingress_ object.
### The `nginx.ingress.kubernetes.io/fastcgi-index` Annotation
To specify an index file, the `fastcgi-index` annotation value can optionally be set. In the example below, the value is set to `index.php`. This annotation corresponds to [the _NGINX_ `fastcgi_index` directive](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_index).
> `nginx.ingress.kubernetes.io/fastcgi-index: "index.php"`
### The `nginx.ingress.kubernetes.io/fastcgi-params-configmap` Annotation
To specify [_NGINX_ `fastcgi_param` directives](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_param), the `fastcgi-params-configmap` annotation is used, which in turn must lead to a _ConfigMap_ object containing the _NGINX_ `fastcgi_param` directives as key/values.
> `nginx.ingress.kubernetes.io/fastcgi-params: "example-configmap"`
And the _ConfigMap_ object to specify the `SCRIPT_FILENAME` and `HTTP_PROXY` _NGINX's_ `fastcgi_param` directives will look like the following:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: example-configmap
data:
SCRIPT_FILENAME: "/example/index.php"
HTTP_PROXY: ""
```
Using the _namespace/_ prefix is also supported, for example:
> `nginx.ingress.kubernetes.io/fastcgi-params: "example-namespace/example-configmap"`

View file

@ -112,6 +112,8 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|[nginx.ingress.kubernetes.io/enable-owasp-core-rules](#modsecurity)|bool|
|[nginx.ingress.kubernetes.io/modsecurity-transaction-id](#modsecurity)|string|
|[nginx.ingress.kubernetes.io/modsecurity-snippet](#modsecurity)|string|
|[nginx.ingress.kubernetes.io/mirror-uri](#mirror)|string|
|[nginx.ingress.kubernetes.io/mirror-request-body](#mirror)|string|
### Canary
@ -817,3 +819,34 @@ By default, a request would need to satisfy all authentication requirements in o
```yaml
nginx.ingress.kubernetes.io/satisfy: "any"
```
### Mirror
Enables a request to be mirrored to a mirror backend. Responses by mirror backends are ignored. This feature is useful, to see how requests will react in "test" backends.
You can mirror a request to the `/mirror` path on your ingress, by applying the below:
```yaml
nginx.ingress.kubernetes.io/mirror-uri: "/mirror"
```
The mirror path can be defined as a separate ingress resource:
```
location = /mirror {
internal;
proxy_pass http://test_backend;
}
```
By default the request-body is sent to the mirror backend, but can be turned off by applying:
```yaml
nginx.ingress.kubernetes.io/mirror-request-body: "off"
```
**Note:** The mirror directive will be applied to all paths within the ingress resource.
The request sent to the mirror is linked to the orignial request. If you have a slow mirror backend, then the orignial request will throttle.
For more information on the mirror module see https://nginx.org/en/docs/http/ngx_http_mirror_module.html

View file

@ -34,7 +34,6 @@ The following table shows a configuration option's name, type, and the default v
|[access-log-path](#access-log-path)|string|"/var/log/nginx/access.log"|
|[enable-access-log-for-default-backend](#enable-access-log-for-default-backend)|bool|"false"|
|[error-log-path](#error-log-path)|string|"/var/log/nginx/error.log"|
|[enable-dynamic-tls-records](#enable-dynamic-tls-records)|bool|"true"|
|[enable-modsecurity](#enable-modsecurity)|bool|"false"|
|[enable-owasp-modsecurity-crs](#enable-owasp-modsecurity-crs)|bool|"false"|
|[client-header-buffer-size](#client-header-buffer-size)|string|"1k"|
@ -149,6 +148,7 @@ The following table shows a configuration option's name, type, and the default v
|[skip-access-log-urls](#skip-access-log-urls)|[]string|[]string{}|
|[limit-rate](#limit-rate)|int|0|
|[limit-rate-after](#limit-rate-after)|int|0|
|[lua-shared-dicts](#lua-shared-dicts)|string|""|
|[http-redirect-code](#http-redirect-code)|int|308|
|[proxy-buffering](#proxy-buffering)|string|"off"|
|[limit-req-status-code](#limit-req-status-code)|int|503|
@ -209,13 +209,6 @@ __Note:__ the file `/var/log/nginx/error.log` is a symlink to `/dev/stderr`
_References:_
[http://nginx.org/en/docs/ngx_core_module.html#error_log](http://nginx.org/en/docs/ngx_core_module.html#error_log)
## enable-dynamic-tls-records
Enables dynamically sized TLS records to improve time-to-first-byte. _**default:**_ is enabled
_References:_
[https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency](https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency)
## enable-modsecurity
Enables the modsecurity module for NGINX. _**default:**_ is disabled
@ -488,6 +481,14 @@ Sets the [SSL protocols](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#
Please check the result of the configuration using `https://ssllabs.com/ssltest/analyze.html` or `https://testssl.sh`.
## ssl-early-data
Enables or disables TLS 1.3 [early data](https://tools.ietf.org/html/rfc8446#section-2.3)
This requires `ssl-protocols` to have `TLSv1.3` enabled.
[ssl_early_data](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data). The default is: `false`.
## ssl-session-cache
Enables or disables the use of shared [SSL cache](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache) among worker processes.
@ -855,6 +856,21 @@ _References:_
Sets the initial amount after which the further transmission of a response to a client will be rate limited.
## lua-shared-dicts
Customize default Lua shared dictionaries or define more. You can use the following syntax to do so:
```
lua-shared-dicts: "<my dict name>: <my dict size>, [<my dict name>: <my dict size>], ..."
```
For example following will set default `certificate_data` dictionary to `100M` and will introduce a new dictionary called
`my_custom_plugin`:
```
lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 5"
```
_References:_
[http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after](http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after)

View file

@ -30,6 +30,7 @@ In addition to the built-in functions provided by the Go package the following f
- hasSuffix: [strings.HasSuffix](https://golang.org/pkg/strings/#HasSuffix)
- toUpper: [strings.ToUpper](https://golang.org/pkg/strings/#ToUpper)
- toLower: [strings.ToLower](https://golang.org/pkg/strings/#ToLower)
- quote: wraps a string in double quotes
- buildLocation: helps to build the NGINX Location section in each server
- buildProxyPass: builds the reverse proxy configuration
- buildRateLimit: helps to build a limit zone inside a location if contains a rate limit annotation

View file

@ -7,7 +7,7 @@ log_format upstreaminfo
'{{ if $cfg.useProxyProtocol }}$proxy_protocol_addr{{ else }}$remote_addr{{ end }} - '
'[$the_real_ip] - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'$request_length $request_time [$proxy_upstream_name] $upstream_addr '
'$request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr '
'$upstream_response_length $upstream_response_time $upstream_status $req_id';
```
@ -26,6 +26,7 @@ log_format upstreaminfo
| `$request_length` | request length (including request line, header, and request body) |
| `$request_time` | time elapsed since the first bytes were read from the client |
| `$proxy_upstream_name` | name of the upstream. The format is `upstream-<namespace>-<service name>-<service port>` |
| `$proxy_alternative_upstream_name` | name of the alternative upstream. The format is `upstream-<namespace>-<service name>-<service port>` |
| `$upstream_addr` | the IP address and port (or the path to the domain socket) of the upstream server. If several servers were contacted during request processing, their addresses are separated by commas. |
| `$upstream_response_length` | the length of the response obtained from the upstream server |
| `$upstream_response_time` | time spent on receiving the response from the upstream server as seconds with millisecond resolution |
@ -45,4 +46,4 @@ Additional available variables:
Sources:
- [Upstream variables](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables)
- [Embedded variables](http://nginx.org/en/docs/http/ngx_http_core_module.html#variables)
- [Embedded variables](http://nginx.org/en/docs/http/ngx_http_core_module.html#variables)

51
go.mod
View file

@ -3,14 +3,16 @@ module k8s.io/ingress-nginx
go 1.12
require (
cloud.google.com/go v0.38.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/Sirupsen/logrus v0.0.0-00010101000000-000000000000 // indirect
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/eapache/channels v1.1.0
github.com/eapache/queue v1.1.0 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect
github.com/go-logr/logr v0.1.0 // indirect
github.com/go-logr/zapr v0.1.1 // indirect
github.com/go-openapi/swag v0.19.0 // indirect
github.com/google/uuid v1.1.1
github.com/imdario/mergo v0.3.7
github.com/json-iterator/go v1.1.6
@ -25,7 +27,6 @@ require (
github.com/ncabatoff/procfs v0.0.0-20180903163354-e1a38cb53622 // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/runc v0.1.1
github.com/parnurzeal/gorequest v0.2.15
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4
@ -34,9 +35,9 @@ require (
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f
github.com/prometheus/common v0.2.0
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb // indirect
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect
github.com/spf13/cobra v0.0.4
github.com/spf13/pflag v1.0.3
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980
@ -46,24 +47,42 @@ require (
gopkg.in/go-playground/pool.v3 v3.1.1
k8s.io/api v0.0.0
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6
k8s.io/apiextensions-apiserver v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227
k8s.io/apiserver v0.0.0
k8s.io/cli-runtime v0.0.0
k8s.io/client-go v12.0.0+incompatible
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5 // indirect
k8s.io/code-generator v0.0.0-20190620073620-d55040311883
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5
k8s.io/klog v0.3.3
k8s.io/kubernetes v0.0.0
k8s.io/code-generator v0.0.0
k8s.io/component-base v0.0.0
k8s.io/klog v0.4.0
k8s.io/kubernetes v1.15.1
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a // indirect
sigs.k8s.io/controller-runtime v0.1.10
sigs.k8s.io/testing_frameworks v0.1.1 // indirect
)
replace (
github.com/Sirupsen/logrus => github.com/sirupsen/logrus v1.4.1
k8s.io/api => k8s.io/api v0.0.0-20190703205437-39734b2a72fe
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76
k8s.io/kubernetes => k8s.io/kubernetes v1.14.3
k8s.io/api => k8s.io/api v0.0.0-20190718183219-b59d8169aab5
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534
k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e
k8s.io/client-go => k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5
k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20190620090010-a60497bb9ffa
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b
k8s.io/component-base => k8s.io/component-base v0.0.0-20190620085131-4cd66be69262
k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20190620090114-816aa063c73d
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20190620085316-c835efc41000
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20190620085943-52018c8ce3c1
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30
k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20190620085811-cc0b23ba60a9
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20190620085909-5dfb14b3a101
k8s.io/kubelet => k8s.io/kubelet v0.0.0-20190620085837-98477dc0c87c
k8s.io/kubernetes => k8s.io/kubernetes v1.15.1
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20190620090159-a9e4f3cb5bf3
k8s.io/metrics => k8s.io/metrics v0.0.0-20190620085627-5b02f62e9559
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20190620085357-8191e314a1f7
)

284
go.sum
View file

@ -1,11 +1,22 @@
bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/Azure/azure-sdk-for-go v21.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v11.1.2+incompatible h1:viZ3tV5l4gE2Sw0xrasFHytCGtzYCrT+um/rrSQ1BfA=
github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20181220005116-f8e995905100/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -13,49 +24,83 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e h1:h0gP0hBU6DsA5IQduhLWGOEfIUKzJS5hhXQBSgHuF/g=
github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM=
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us=
github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/client9/misspell v0.0.0-20170928000206-9ce5d979ffda/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0=
github.com/codedellemc/goscaleio v0.0.0-20170830184815-20e2ce2cf885/go.mod h1:JIHmDHNZO4tmA3y3RHp6+Gap6kFsNf55W9Pn/3YS9IY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
github.com/containernetworking/cni v0.6.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U=
github.com/cpuguy83/go-md2man v1.0.4/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cyphar/filepath-securejoin v0.0.0-20170720062807-ae69057f2299/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v0.0.0-20170829104524-6e570ed0a266/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda h1:NyywMz59neOoVRFDz+ccfKWxn784fiHMDnZSy6T+JXY=
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff h1:FKH02LHYqSmeWd3GBh0KIkM8JBpw3RrShgtcWShdWJg=
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/ekalinin/github-markdown-toc v0.0.0-20190514155158-83fadb60a7f1/go.mod h1:XfZS1iyC28CnllR54Ou2Ero6qs4Rmn7GpVumNSj1DZo=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1Sh1ZJSDm4A4iKLS5QNbvUHMgGu/M=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v0.0.0-20160318181535-f6a740d52f96/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU=
@ -75,36 +120,30 @@ github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpR
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M=
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k=
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
@ -113,29 +152,45 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v0.0.0-20160127222235-bd3c8e81be01/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/gomarkdown/markdown v0.0.0-20190222000725-ee6a7931a1e4 h1:vELsocEzlhM4lk2nhxolEaQrMp25u7/i9IX8s9uLads=
github.com/gomarkdown/markdown v0.0.0-20190222000725-ee6a7931a1e4/go.mod h1:gmFANS06wAVmF0B9yi65QKsRmPQ97tze7FRLswua+OY=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4=
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cadvisor v0.33.2-0.20190411163913-9db8c7dee20a/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8 h1:L9JPKrtsHMQ4VCRQfHvbbHBfB2Urn8xf6QZeXZ+OrN4=
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -143,7 +198,12 @@ github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20160711231752-d8c773c4cba1/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/heketi/heketi v0.0.0-20181109135656-558b29266ce0/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o=
github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI=
github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4=
github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -151,48 +211,74 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jteeuwen/go-bindata v0.0.0-20151023091102-a0ff2567cfb7/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20150410034420-8fef92e41e22/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.0.0-20140812000539-f31442d60e51/go.mod h1:Bvhd+E3laJ0AVkG0c9rmtZcnhV0HQ3+c3YxxqTvc/gA=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.0.0-20130911015532-6807e777504f/go.mod h1:sjUstKUATFIcff4qlB53Kml0wQPtJVc/3fWrmuUmcfA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/libopenstorage/openstorage v0.0.0-20170906232338-093a0c388875/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA=
github.com/magiconair/properties v0.0.0-20160816085511-61b492c03cf4/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/mattn/go-shellwords v0.0.0-20180605041737-f8471b0a71de/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4=
github.com/mholt/caddy v0.0.0-20180213163048-2de495001514/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
github.com/miekg/dns v0.0.0-20160614162101-5d001d020961/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY=
github.com/mistifyio/go-zfs v0.0.0-20151009155749-1b4ae6fb4e77/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mmarkdown/mmark v2.0.40+incompatible h1:vMeUeDzBK3H+/mU0oMVfMuhSXJlIA+DE/DMPQNAj5C4=
github.com/mmarkdown/mmark v2.0.40+incompatible/go.mod h1:Uvmoz7tvsWpr7bMVxIpqZPyN3FbOtzDmnsJDFp7ltJs=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52 h1:8zDEa5yAIWYBHSDpPbSgGIBL/SvPSE9/FlB3aQ54d/A=
github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52/go.mod h1:jE2HT8eoucYyUPBFJMreiVlC3KPHkDMtN8wn+ef7Y64=
github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mvdan/xurls v0.0.0-20160110113200-1b768d7c393a/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833 h1:t4WWQ9I797y7QUgeEjeXnVb+oYuEDQc6gLvrZJTYo94=
github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833/go.mod h1:0CznHmXSjMEqs5Tezj/w2emQoM41wzYM9KpDKUHPYag=
github.com/ncabatoff/process-exporter v0.0.0-20180915144445-bdf24ef23850 h1:+N9D9mM5Nr4iDYOrZBVDT7w7wGhFNTvEXWX80LNXJMo=
@ -202,26 +288,34 @@ github.com/ncabatoff/procfs v0.0.0-20180903163354-e1a38cb53622/go.mod h1:1Sa4zSa
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v0.0.0-20170604055404-372ad780f634/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.0.0-20181113202123-f000fe11ece1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v0.0.0-20170621221121-4a2974bf1ee9/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU=
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4 h1:bOK8V55gl1AEWE9KiXSZ0fzARvVBehdmYZOTFcQ5kUo=
github.com/paultag/sniff v0.0.0-20170624152000-87325c3dddf4/go.mod h1:J3XXNGJINXLa4yIivdUT0Ad/srv2q0pSOWbbm6El2EY=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v0.0.0-20160930220758-4d0e916071f6/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
@ -237,46 +331,68 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb h1:LvNCMEj0FFZQYsxZb7o3xQPrtqOOB6lrTUOWshC+ZTs=
github.com/prometheus/procfs v0.0.0-20190328153300-af7bedc223fb/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/robfig/cron v0.0.0-20170309132418-df38d32658d8/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v0.0.0-20151117072312-300106c228d5/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20151028001915-10ef21a441db/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sigma/go-inotify v0.0.0-20181102212354-c87b6cf5033d/go.mod h1:stlh9OsqBQSdwxTxX73mu41BBtRbIpZLQ7flcAoxAfo=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI=
github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v0.0.0-20160816080757-b28a7effac97/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v0.0.0-20160730092037-e31f36ffc91a/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE=
github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v0.0.0-20160311093646-33c24e77fb80/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v0.0.0-20160820190039-7fb2782df3d8/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813 h1:1Q3NGZNH8/oSGhSe0J8WjkFCeIOb0JSy7M4RpqY64XI=
github.com/tallclair/mdtoc v0.0.0-20190627191617-4dc3d6f90813/go.mod h1:BjDk9nfX4091pXLHhvf6Ejr4/r//9NslWmweWb2Hkbs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/vmware/photon-controller-go-sdk v0.0.0-20170310013346-4a435daef6cc/go.mod h1:e6humHha1ekIwTCm+A5Qed5mG8V4JL+ChHcUOJ+L/8U=
github.com/xanzy/go-cloudstack v0.0.0-20160728180336-1e2cbf647e57/go.mod h1:s3eL3z5pNXF5FVybcT+LIVdId8pYn709yv6v5mrkrQE=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272 h1:scDk3LAM8x+NPuywVGC0q0/G+5Ed8M0+YXecz4XnWRM=
github.com/zakjan/cert-chain-resolver v0.0.0-20180703112424-6076e1ded272/go.mod h1:KNkcm66cr4ilOiEcjydK+tc2ShPUhqmuoXCljXUBPu8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
@ -286,17 +402,17 @@ go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslx
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -307,51 +423,55 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485 h1:OB/uP/Puiu5vS5QMRPrXCDWUPb+kt8f1KW8oQzFejQw=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e h1:jRyg0XfpwWlhEV8mDfdNGBeSJM2fuyh9Yjrnd8kF2Ts=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
@ -359,7 +479,11 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7 h1:ZUjXAXmrAyrmmCPHgCA/vChHcpsX27MZ3yBonD/z1KE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -370,54 +494,67 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/pool.v3 v3.1.1 h1:4Qcj91IsYTpIeRhe/eo6Fz+w6uKWPEghx8vHFTYMfhw=
gopkg.in/go-playground/pool.v3 v3.1.1/go.mod h1:pUAGBximS/hccTTSzEop6wvvQhVa3QPDFFW+8REdutg=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190703205437-39734b2a72fe h1:MFaHtAyhZcfBZocN91muHSqnwiF5yfXx7yGoehneNYg=
k8s.io/api v0.0.0-20190703205437-39734b2a72fe/go.mod h1:J5EZ0KSEjvyKOBy5BDHSF3zn82madLLWg7nUKaOHZKU=
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6 h1:qmQstpcwzWz3sF4IdIdHNd+QqEW27NtIl3g1Hc64pKo=
k8s.io/apiextensions-apiserver v0.0.0-20190626210203-fdc73e13f9a6/go.mod h1:BtPMQLDJSVfn7fHhoZrhbSHNrEFnXjKNVefNBc7M4/c=
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76 h1:vxMYBaJgczGAIpJAOBco2eHuFYIyDdNIebt60jxLauA=
k8s.io/apimachinery v0.0.0-20190703205208-4cfb76a8bf76/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA=
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c h1:hQTBSsQW7vz11pD5H+u8CJDPH5tPMZU7fZNrngblrkI=
k8s.io/apiserver v0.0.0-20190625052034-8c075cba2f8c/go.mod h1:iv88XxGh9TIPUnSeQVGhypLx6/n0xn1/8OxMRrkKUP4=
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227 h1:q8pjgm8GrV8Zsnb2bdy4SHM41rASdMH59D8LHQDoKQM=
k8s.io/cli-runtime v0.0.0-20190711111425-61e036b70227/go.mod h1:ZJEA9p+Wpusb5qdOk+B2TSXNY6xFrcduZrWaVaZI0V4=
k8s.io/client-go v0.0.0-20190624085356-2c6e35a5b9cf/go.mod h1:rQMvHbaXi4pHSLf91Z1YI3h2Av+T3jsFKEIAWucw7hc=
k8s.io/client-go v0.0.0-20190626045420-1ec4b74c7bda/go.mod h1:+MutroXOEjjw9n8kfVq8ZJEE/Fk3YVww/+4uIg+m/S0=
k8s.io/client-go v0.0.0-20190711103903-4a0861cac5e0/go.mod h1:MdhWxiGHTqqubuL4pqFLTAhEjVr0Oa6nj8mAy3kFIEI=
k8s.io/client-go v12.0.0+incompatible h1:YlJxncpeVUC98/WMZKC3JZGk/OXQWCZjAB4Xr3B17RY=
k8s.io/client-go v12.0.0+incompatible/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5 h1:sRUsF1Fop2aeMJOfCT4xlQJUvMhk+EznyzY7RMW6ZzQ=
k8s.io/cloud-provider v0.0.0-20190711113108-0d51509e0ef5/go.mod h1:5LDt9VVqCTfW59z7dQ8QXZ3L+CwHP/6P1rRSBCgdb3o=
k8s.io/code-generator v0.0.0-20190620073620-d55040311883 h1:NWWNvN6IdpmQvZ43rVccCI8GPUrheK8XNdqeKycw0DI=
k8s.io/code-generator v0.0.0-20190620073620-d55040311883/go.mod h1:+a+9g9W0llgbgvx6qOb+VbeZPH5km1FrVyMQe9/jkQY=
k8s.io/component-base v0.0.0-20190624085813-dd74dcc4bb91/go.mod h1:rzptoTTLijQSUO6SiDWhyXGRbVquvfM7QY0fvdPyXSU=
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5 h1:q/LCzptcxSw9EyZFILhgkvgSh3TBPM8UhLWCq19sV7I=
k8s.io/component-base v0.0.0-20190626045757-ca439aa083f5/go.mod h1:rzptoTTLijQSUO6SiDWhyXGRbVquvfM7QY0fvdPyXSU=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190718183219-b59d8169aab5 h1:X3LHYU4fwu75lvvWypbppCKuhqg1KrvcZ1lLaAgmE/g=
k8s.io/api v0.0.0-20190718183219-b59d8169aab5/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9 h1:k9vwVsnIfkX2eTHTTfJoYo6pGwwVBUAr7VZzinSrjLM=
k8s.io/apiextensions-apiserver v0.0.0-20190620085550-3a2f62f126c9/go.mod h1:di7nkarEFC6V6WvJIq4rLpWymXYPM3wwqZFtYIA0Xg0=
k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534 h1:Iz41otzY4YmgjBECtotKCfHHKm9FSZ5x/1ykagnI30s=
k8s.io/apimachinery v0.0.0-20190711103026-7bf792636534/go.mod h1:M2fZgZL9DbLfeJaPBCDqSqNsdsmLN+V29knYJnIXlMA=
k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7 h1:8mEhvrlegNEsADNHBah9RiCpEGaG1WVLall4pitumBs=
k8s.io/apiserver v0.0.0-20190718184206-a1aa83af71a7/go.mod h1:U+p3oErZ9CNJpjYE9Kx0IZ0VHFiDC92/xM4znOfwqaU=
k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e h1:54KOxJkSycxbYrueshmFp1cUAfVbEP9sjYKVbIXYw9M=
k8s.io/cli-runtime v0.0.0-20190620085659-429467d76d0e/go.mod h1:nxflCnMaMcLCeQNRLyGeoY2crlsPMVmCPc0iGTUvajE=
k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5 h1:ZIHnBytv9H1jiI7K/Szva829lP6zKsluhjVRdxf5kHA=
k8s.io/client-go v0.0.0-20190718183610-8e956561bbf5/go.mod h1:ozblAqkW495yoAX60QZyxQBq5W0YixE9Ffn4F91RO0g=
k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd h1:3rNeJmlNQjzcuUi6h1loRsqKjMQSxWDAWYDMittuTGE=
k8s.io/cloud-provider v0.0.0-20190620090041-1a7e1f6630cd/go.mod h1:Un5YLpjmC+XZ9zO19edNEnyy9bQ9gt2H210665c4FWc=
k8s.io/cluster-bootstrap v0.0.0-20190620090010-a60497bb9ffa/go.mod h1:nKdJhweDROdvToPucD2iin6nuJ0zg8qRxBu2i/LqxL0=
k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b h1:p+PRuwXWwk5e+UYvicGiavEupapqM5NOxUl3y1GkD6c=
k8s.io/code-generator v0.0.0-20190612205613-18da4a14b22b/go.mod h1:G8bQwmHm2eafm5bgtX67XDZQ8CWKSGu9DekI+yN4Y5I=
k8s.io/component-base v0.0.0-20190620085131-4cd66be69262 h1:2j4mE+jKntnIcQjOOWyzb/2GsJIuPqjORLMDeHnagpM=
k8s.io/component-base v0.0.0-20190620085131-4cd66be69262/go.mod h1:VLedAFwENz2swOjm0zmUXpAP2mV55c49xgaOzPBI/QQ=
k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1 h1:NVZIgq492plCUB4GlZUTdgGQRPOKmBWpw2HlOJDm9OQ=
k8s.io/cri-api v0.0.0-20190531030430-6117653b35f1/go.mod h1:K6Ux7uDbzKhacgqW0OJg3rjXk/SR9kprCPfSUDXGB5A=
k8s.io/csi-translation-lib v0.0.0-20190620090114-816aa063c73d/go.mod h1:q5k0Vv3qsOTu2PUrTh+4mRAj+Pt+1BRyWiiwr5LBFOM=
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af h1:SwjZbO0u5ZuaV6TRMWOGB40iaycX8sbdMQHtjNZ19dk=
k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c=
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-aggregator v0.0.0-20190620085316-c835efc41000/go.mod h1:VQ+wumV2o4KXe3arqjS/+4l3Hd+3cZOOCf1wcGPzPJg=
k8s.io/kube-controller-manager v0.0.0-20190620085943-52018c8ce3c1/go.mod h1:E2bR8dj02P7Ydp/oaGjos0ydxIps1dcqvA21BsAGr9I=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kubernetes v1.14.3 h1:/FQkOJpjc1jGA37s7Rt3U10VwIKW685ejrgOp4UDRFE=
k8s.io/kubernetes v1.14.3/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/kube-proxy v0.0.0-20190620085811-cc0b23ba60a9/go.mod h1:s7TcaJtcwsGGyi9q1gmmDuTBwJcJn/rfyAOIUUh3JjI=
k8s.io/kube-scheduler v0.0.0-20190620085909-5dfb14b3a101/go.mod h1:aiWD0dWmuCnURY1+o3E0WgKQOlWFFqHESjz4nhzXXh8=
k8s.io/kubelet v0.0.0-20190620085837-98477dc0c87c/go.mod h1:L9fEppwvpkTrPZju8XolATHatuUt4Vscd/SVHZZGHKc=
k8s.io/kubernetes v1.15.1 h1:bCoCfn9sRFf47U5wn/y6I397hduMEpJ2gh4uN8BUYGI=
k8s.io/kubernetes v1.15.1/go.mod h1:3RE5ikMc73WK+dSxk4pQuQ6ZaJcPXiZX2dj98RcdCuM=
k8s.io/legacy-cloud-providers v0.0.0-20190620090159-a9e4f3cb5bf3/go.mod h1:vN4qQ5sBl+8105txtOVlFFNAXTc8lYvM41krV0K7xXc=
k8s.io/metrics v0.0.0-20190620085627-5b02f62e9559/go.mod h1:f5M7Wu7aoJPaYxys9OpQdzhfOVWhuS/WE20voRy8KEY=
k8s.io/repo-infra v0.0.0-20181204233714-00fe14e3d1a3/go.mod h1:+G1xBfZDfVFsm1Tj/HNCvg4QqWx8rJ2Fxpqr1rqp/gQ=
k8s.io/sample-apiserver v0.0.0-20190620085357-8191e314a1f7/go.mod h1:RVMdQ03C5ZqPz82uV82L72wju27+zFFnyzKje9DVBZI=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
k8s.io/utils v0.0.0-20190607212802-c55fbcfc754a h1:2jUDc9gJja832Ftp+QbDV0tVhQHMISFn01els+2ZAcw=
@ -436,3 +573,4 @@ sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkW
sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=

View file

@ -21,5 +21,6 @@ limitations under the License.
package tools
import (
_ "github.com/tallclair/mdtoc"
_ "k8s.io/code-generator"
)

34
hack/update-toc.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Copyright 2019 The Kubernetes 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.
set -o errexit
set -o nounset
set -o pipefail
export KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
# Install tools we need, but only from vendor/...
cd ${KUBE_ROOT}
go install ./vendor/github.com/tallclair/mdtoc
if ! which mdtoc >/dev/null 2>&1; then
echo "Can't find mdtoc - is your GOPATH 'bin' in your PATH?" >&2
echo " GOPATH: ${GOPATH}" >&2
echo " PATH: ${PATH}" >&2
exit 1
fi
# Update tables of contents if necessary.
grep --include='*.md' -rl docs/enhancements/* -e '<!-- toc -->' | xargs mdtoc --inplace

View file

@ -0,0 +1,105 @@
all: all-container
BUILDTAGS=
# Use the 0.0 tag for testing, it shouldn't clobber any release builds
TAG?=0.1
REGISTRY?=kubernetes-ingress-controller
GOOS?=linux
DOCKER?=docker
SED_I?=sed -i
GOHOSTOS ?= $(shell go env GOHOSTOS)
PKG=k8s.io/ingress-nginx/images/fastcgi-helloserver
ifeq ($(GOHOSTOS),darwin)
SED_I=sed -i ''
endif
REPO_INFO=$(shell git config --get remote.origin.url)
ARCH ?= $(shell go env GOARCH)
GOARCH = ${ARCH}
BASEIMAGE?=alpine:3.9
ALL_ARCH = amd64 arm arm64 ppc64le
QEMUVERSION=v3.0.0
IMGNAME = fastcgi-helloserver
IMAGE = $(REGISTRY)/$(IMGNAME)
MULTI_ARCH_IMG = $(IMAGE)-$(ARCH)
ifeq ($(ARCH),arm)
QEMUARCH=arm
GOARCH=arm
endif
ifeq ($(ARCH),arm64)
QEMUARCH=aarch64
endif
ifeq ($(ARCH),ppc64le)
QEMUARCH=ppc64le
GOARCH=ppc64le
endif
TEMP_DIR := $(shell mktemp -d)
DOCKERFILE := $(TEMP_DIR)/rootfs/Dockerfile
sub-container-%:
$(MAKE) ARCH=$* build container
sub-push-%:
$(MAKE) ARCH=$* push
all-container: $(addprefix sub-container-,$(ALL_ARCH))
all-push: $(addprefix sub-push-,$(ALL_ARCH))
container: .container-$(ARCH)
.container-$(ARCH):
cp -r ./* $(TEMP_DIR)
$(SED_I) 's|BASEIMAGE|$(BASEIMAGE)|g' $(DOCKERFILE)
$(SED_I) "s|QEMUARCH|$(QEMUARCH)|g" $(DOCKERFILE)
ifeq ($(ARCH),amd64)
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
$(SED_I) "/CROSS_BUILD_/d" $(DOCKERFILE)
else
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
# $(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMUVERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(TEMP_DIR)/rootfs
$(SED_I) "s/CROSS_BUILD_//g" $(DOCKERFILE)
endif
$(DOCKER) build -t $(MULTI_ARCH_IMG):$(TAG) $(TEMP_DIR)/rootfs
ifeq ($(ARCH), amd64)
# This is for to maintain the backward compatibility
$(DOCKER) tag $(MULTI_ARCH_IMG):$(TAG) $(IMAGE):$(TAG)
endif
push: .push-$(ARCH)
.push-$(ARCH):
$(DOCKER) push $(MULTI_ARCH_IMG):$(TAG)
ifeq ($(ARCH), amd64)
$(DOCKER) push $(IMAGE):$(TAG)
endif
clean:
$(DOCKER) rmi -f $(MULTI_ARCH_IMG):$(TAG) || true
build: clean
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -a -installsuffix cgo \
-ldflags "-s -w" \
-o ${TEMP_DIR}/rootfs/fastcgi-helloserver ${PKG}/...
release: all-container all-push
echo "done"
.PHONY: register-qemu
register-qemu:
# Register /usr/bin/qemu-ARCH-static as the handler for binaries in multiple platforms
$(DOCKER) run --rm --privileged multiarch/qemu-user-static:register --reset

View file

@ -0,0 +1,30 @@
package main
import (
"fmt"
"net"
"net/http"
"net/http/fcgi"
)
func hello(w http.ResponseWriter, r *http.Request) {
keys, ok := r.URL.Query()["name"]
if !ok || len(keys[0]) < 1 {
fmt.Fprintf(w, "Hello world!")
return
}
key := keys[0]
fmt.Fprintf(w, "Hello "+string(key)+"!")
}
func main() {
http.HandleFunc("/hello", hello)
l, err := net.Listen("tcp", "0.0.0.0:9000")
if err != nil {
panic(err)
}
fcgi.Serve(l, nil)
}

View file

@ -0,0 +1,21 @@
# Copyright 2017 The Kubernetes 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.
FROM BASEIMAGE
CROSS_BUILD_COPY qemu-QEMUARCH-static /usr/bin/
COPY . /
CMD ["/fastcgi-helloserver"]

View file

@ -13,7 +13,7 @@
# limitations under the License.
# 0.0.0 shouldn't clobber any released builds
TAG ?= 0.90
TAG ?= 0.92
REGISTRY ?= quay.io/kubernetes-ingress-controller
ARCH ?= $(shell go env GOARCH)
DOCKER ?= docker

View file

@ -21,7 +21,7 @@ set -o pipefail
export DEBIAN_FRONTEND=noninteractive
export OPENRESTY_VERSION=1.15.8.1
export OPENRESTY_VERSION=1.15.8.2
export NGINX_DIGEST_AUTH=cd8641886c873cf543255aeda20d23e4cd603d05
export NGINX_SUBSTITUTIONS=bc58cb11844bc42735bbaef7085ea86ace46d05b
export NGINX_OPENTRACING_VERSION=0.8.0
@ -29,7 +29,7 @@ export OPENTRACING_CPP_VERSION=1.5.1
export ZIPKIN_CPP_VERSION=0.5.2
export JAEGER_VERSION=cdfaf5bb25ff5f8ec179fd548e6c7c2ade9a6a09
export MSGPACK_VERSION=3.1.1
export DATADOG_CPP_VERSION=0.4.3
export DATADOG_CPP_VERSION=1.0.1
export MODSECURITY_VERSION=d7101e13685efd7e7c9f808871b202656a969f4b
export MODSECURITY_LIB_VERSION=3.0.3
export OWASP_MODSECURITY_CRS_VERSION=3.1.0
@ -152,7 +152,7 @@ get_src 3183450d897baa9309347c8617edc0c97c5b29ffc32bd2d12f498edf2dcbeffa \
get_src bda49f996a73d2c6080ff0523e7b535917cd28c8a79c3a5da54fc29332d61d1e \
"https://github.com/msgpack/msgpack-c/archive/cpp-$MSGPACK_VERSION.tar.gz"
get_src 7ef075c5936cfcca37d32c3b83b3b05d86f8a919d61fc94634f97a5c6542cff4 \
get_src f7fb2ad541f812c36fd78f9a38e4582d87dadb563ab80bee3f7c3a2132a425c5 \
"https://github.com/DataDog/dd-opentracing-cpp/archive/v$DATADOG_CPP_VERSION.tar.gz"
get_src f5470132d8756eef293833e30508926894883924a445e3b9a07c869d26d4706d \

View file

@ -16,92 +16,5 @@ limitations under the License.
package file
import (
"fmt"
"os"
"path/filepath"
"strings"
"k8s.io/kubernetes/pkg/util/filesystem"
)
// ReadWriteByUser defines linux permission to read and write files for the owner user
const ReadWriteByUser = 0660
// ReadByUserGroup defines linux permission to read files by the user and group owner/s
const ReadByUserGroup = 0640
// Filesystem is an interface that we can use to mock various filesystem operations
type Filesystem interface {
filesystem.Filesystem
}
// NewLocalFS implements Filesystem using same-named functions from "os" and "io/ioutil".
func NewLocalFS() (Filesystem, error) {
fs := filesystem.DefaultFs{}
for _, directory := range directories {
err := fs.MkdirAll(directory, ReadWriteByUser)
if err != nil {
return nil, err
}
}
return fs, nil
}
// NewFakeFS creates an in-memory filesystem with all the required
// paths used by the ingress controller.
// This allows running test without polluting the local machine.
func NewFakeFS() (Filesystem, error) {
osFs := filesystem.DefaultFs{}
fakeFs := filesystem.NewFakeFs()
//TODO: find another way to do this
rootFS := filepath.Clean(fmt.Sprintf("%v/%v", os.Getenv("GOPATH"), "src/k8s.io/ingress-nginx/rootfs"))
var fileList []string
err := filepath.Walk(rootFS, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
file := strings.TrimPrefix(path, rootFS)
if file == "" {
return nil
}
fileList = append(fileList, file)
return nil
})
if err != nil {
return nil, err
}
for _, file := range fileList {
realPath := fmt.Sprintf("%v%v", rootFS, file)
data, err := osFs.ReadFile(realPath)
if err != nil {
return nil, err
}
fakeFile, err := fakeFs.Create(file)
if err != nil {
return nil, err
}
_, err = fakeFile.Write(data)
if err != nil {
return nil, err
}
}
return fakeFs, nil
}

View file

@ -1,37 +0,0 @@
/*
Copyright 2017 The Kubernetes 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.
*/
package file
import (
"testing"
)
func TestNewFakeFS(t *testing.T) {
fs, err := NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem abstraction: %v", err)
}
if fs == nil {
t.Fatal("expected a filesystem but none returned")
}
_, err = fs.Stat("/etc/nginx/nginx.conf")
if err != nil {
t.Fatalf("unexpected error reading default nginx.conf file: %v", err)
}
}

View file

@ -39,12 +39,14 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
@ -83,6 +85,7 @@ type Ingress struct {
CustomHTTPErrors []int
DefaultBackend *apiv1.Service
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
FastCGI fastcgi.Config
Denied *string
ExternalAuth authreq.Config
EnableGlobalAuth bool
@ -109,6 +112,7 @@ type Ingress struct {
LuaRestyWAF luarestywaf.Config
InfluxDB influxdb.Config
ModSecurity modsecurity.Config
Mirror mirror.Config
}
// Extractor defines the annotation parsers to be used in the extraction of annotations
@ -130,6 +134,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"CorsConfig": cors.NewParser(cfg),
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
"DefaultBackend": defaultbackend.NewParser(cfg),
"FastCGI": fastcgi.NewParser(cfg),
"ExternalAuth": authreq.NewParser(cfg),
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
@ -156,6 +161,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"InfluxDB": influxdb.NewParser(cfg),
"BackendProtocol": backendprotocol.NewParser(cfg),
"ModSecurity": modsecurity.NewParser(cfg),
"Mirror": mirror.NewParser(cfg),
},
}
}

View file

@ -68,7 +68,7 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
return &resolver.AuthSSLCert{
Secret: name,
CAFileName: "/opt/ca.pem",
PemSHA: "123",
CASHA: "123",
}, nil
}
return nil, nil

View file

@ -86,12 +86,7 @@ func (e1 *Config) Equal(e2 *Config) bool {
return false
}
match = sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
if !match {
return false
}
return true
return sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
}
var (

View file

@ -168,7 +168,7 @@ func TestHeaderAnnotations(t *testing.T) {
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", err.Error())
t.Error("expected error but retuned nil")
}
continue
}
@ -216,7 +216,7 @@ func TestCacheDurationAnnotations(t *testing.T) {
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", err.Error())
t.Errorf("expected error but retuned nil")
}
continue
}

View file

@ -77,7 +77,7 @@ func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, erro
return &resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}, nil
}
@ -202,12 +202,12 @@ func TestEquals(t *testing.T) {
sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}
sslCert2 := resolver.AuthSSLCert{
Secret: "default/other-demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}
cfg1.AuthSSLCert = sslCert1
cfg2.AuthSSLCert = sslCert2

View file

@ -31,7 +31,7 @@ import (
const HTTP = "HTTP"
var (
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
)
type backendProtocol struct {

View file

@ -0,0 +1,107 @@
/*
Copyright 2018 The Kubernetes 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.
*/
package fastcgi
import (
"fmt"
"reflect"
"github.com/pkg/errors"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type fastcgi struct {
r resolver.Resolver
}
// Config describes the per location fastcgi config
type Config struct {
Index string `json:"index"`
Params map[string]string `json:"params"`
}
// Equal tests for equality between two Configuration types
func (l1 *Config) Equal(l2 *Config) bool {
if l1 == l2 {
return true
}
if l1 == nil || l2 == nil {
return false
}
if l1.Index != l2.Index {
return false
}
return reflect.DeepEqual(l1.Params, l2.Params)
}
// NewParser creates a new fastcgiConfig protocol annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return fastcgi{r}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate the fastcgiConfig.
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
fcgiConfig := Config{}
if ing.GetAnnotations() == nil {
return fcgiConfig, nil
}
index, err := parser.GetStringAnnotation("fastcgi-index", ing)
if err != nil {
index = ""
}
fcgiConfig.Index = index
cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing)
if err != nil {
return fcgiConfig, nil
}
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error reading configmap name from annotation"),
}
}
if cmns == "" {
cmns = ing.Namespace
}
cm = fmt.Sprintf("%v/%v", cmns, cmn)
cmap, err := a.r.GetConfigMap(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
Reason: errors.Wrapf(err, "unexpected error reading configmap %v", cm),
}
}
fcgiConfig.Params = cmap.Data
return fcgiConfig, nil
}

View file

@ -0,0 +1,263 @@
/*
Copyright 2018 The Kubernetes 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.
*/
package fastcgi
import (
"testing"
api "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "fastcgi",
ServicePort: intstr.FromInt(80),
},
},
}
}
type mockConfigMap struct {
resolver.Mock
}
func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) {
if name != "default/demo-configmap" {
return nil, errors.Errorf("there is no configmap with name %v", name)
}
return &api.ConfigMap{
ObjectMeta: meta_v1.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "demo-secret",
},
Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}, nil
}
func TestParseEmptyFastCGIAnnotations(t *testing.T) {
ing := buildIngress()
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if config.Index != "" {
t.Errorf("Index should be an empty string")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
func TestParseFastCGIIndexAnnotation(t *testing.T) {
ing := buildIngress()
const expectedAnnotation = "index.php"
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if config.Index != "index.php" {
t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index)
}
}
func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = ""
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
ing := buildIngress()
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
for _, configmap := range invalidConfigMapList {
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err == nil {
t.Errorf("Reading an unexisting configmap should return an error")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
}
func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap"
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 2 != len(config.Params) {
t.Errorf("Params should have a length of 2")
}
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
t.Errorf("Params value is not the one expected")
}
}
func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap"
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 2 != len(config.Params) {
t.Errorf("Params should have a length of 2")
}
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
t.Errorf("Params value is not the one expected")
}
}
func TestConfigEquality(t *testing.T) {
var nilConfig *Config
config := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}
configCopy := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}
config2 := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200"},
}
config3 := Config{
Index: "index.py",
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
}
config4 := Config{
Index: "index.php",
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
}
if !config.Equal(&config) {
t.Errorf("config should be equal to itself")
}
if nilConfig.Equal(&config) {
t.Errorf("Foo")
}
if !config.Equal(&configCopy) {
t.Errorf("config should be equal to configCopy")
}
if config.Equal(&config2) {
t.Errorf("config2 should not be equal to config")
}
if config.Equal(&config3) {
t.Errorf("config3 should not be equal to config")
}
if !config.Equal(&config4) {
t.Errorf("config4 should be equal to config")
}
}

View file

@ -0,0 +1,58 @@
/*
Copyright 2019 The Kubernetes 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.
*/
package mirror
import (
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
// Config returns the mirror to use in a given location
type Config struct {
URI string `json:"uri"`
RequestBody string `json:"requestBody"`
}
type mirror struct {
r resolver.Resolver
}
// NewParser creates a new mirror configuration annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return mirror{r}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure mirror
func (a mirror) Parse(ing *networking.Ingress) (interface{}, error) {
config := &Config{}
var err error
config.URI, err = parser.GetStringAnnotation("mirror-uri", ing)
if err != nil {
config.URI = ""
}
config.RequestBody, err = parser.GetStringAnnotation("mirror-request-body", ing)
if err != nil || config.RequestBody != "off" {
config.RequestBody = "on"
}
return config, nil
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2019 The Kubernetes 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.
*/
package mirror
import (
"fmt"
"reflect"
"testing"
api "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func TestParse(t *testing.T) {
uri := parser.GetAnnotationWithPrefix("mirror-uri")
requestBody := parser.GetAnnotationWithPrefix("mirror-request-body")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected *Config
}{
{map[string]string{uri: "/mirror", requestBody: ""}, &Config{
URI: "/mirror",
RequestBody: "on",
}},
{map[string]string{uri: "/mirror", requestBody: "off"}, &Config{
URI: "/mirror",
RequestBody: "off",
}},
{map[string]string{uri: "", requestBody: "ahh"}, &Config{
URI: "",
RequestBody: "on",
}},
{map[string]string{uri: "", requestBody: ""}, &Config{
URI: "",
RequestBody: "on",
}},
{map[string]string{}, &Config{
URI: "",
RequestBody: "on",
}},
{nil, &Config{
URI: "",
RequestBody: "on",
}},
}
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
fmt.Printf("%t", result)
if !reflect.DeepEqual(result, testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -25,22 +25,23 @@ import (
// Config returns the proxy timeout to use in the upstream server/s
type Config struct {
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BuffersNumber int `json:"buffersNumber"`
BufferSize string `json:"bufferSize"`
CookieDomain string `json:"cookieDomain"`
CookiePath string `json:"cookiePath"`
NextUpstream string `json:"nextUpstream"`
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
NextUpstreamTries int `json:"nextUpstreamTries"`
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
ProxyRedirectTo string `json:"proxyRedirectTo"`
RequestBuffering string `json:"requestBuffering"`
ProxyBuffering string `json:"proxyBuffering"`
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BuffersNumber int `json:"buffersNumber"`
BufferSize string `json:"bufferSize"`
CookieDomain string `json:"cookieDomain"`
CookiePath string `json:"cookiePath"`
NextUpstream string `json:"nextUpstream"`
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
NextUpstreamTries int `json:"nextUpstreamTries"`
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
ProxyRedirectTo string `json:"proxyRedirectTo"`
RequestBuffering string `json:"requestBuffering"`
ProxyBuffering string `json:"proxyBuffering"`
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
ProxyMaxTempFileSize string `json:"proxyMaxTempFileSize"`
}
// Equal tests for equality between two Configuration types
@ -100,6 +101,10 @@ func (l1 *Config) Equal(l2 *Config) bool {
return false
}
if l1.ProxyMaxTempFileSize != l2.ProxyMaxTempFileSize {
return false
}
return true
}
@ -200,5 +205,10 @@ func (a proxy) Parse(ing *networking.Ingress) (interface{}, error) {
config.ProxyHTTPVersion = defBackend.ProxyHTTPVersion
}
config.ProxyMaxTempFileSize, err = parser.GetStringAnnotation("proxy-max-temp-file-size", ing)
if err != nil {
config.ProxyMaxTempFileSize = defBackend.ProxyMaxTempFileSize
}
return config, nil
}

View file

@ -82,6 +82,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
ProxyRequestBuffering: "on",
ProxyBuffering: "off",
ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m",
}
}
@ -101,6 +102,7 @@ func TestProxy(t *testing.T) {
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
@ -147,6 +149,9 @@ func TestProxy(t *testing.T) {
if p.ProxyHTTPVersion != "1.0" {
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
}
if p.ProxyMaxTempFileSize != "128k" {
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
}
}
func TestProxyWithNoAnnotation(t *testing.T) {
@ -196,4 +201,7 @@ func TestProxyWithNoAnnotation(t *testing.T) {
if p.ProxyHTTPVersion != "1.1" {
t.Errorf("expected 1.1 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
}
if p.ProxyMaxTempFileSize != "1024m" {
t.Errorf("expected 1024m as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
}
}

View file

@ -85,7 +85,7 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
}
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
t.Errorf("expected INGRESSCOOKIE as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
}
if nginxAffinity.Cookie.Expires != "4500" {

View file

@ -18,6 +18,7 @@ package controller
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
@ -63,7 +64,7 @@ func (n *NGINXController) Check(_ *http.Request) error {
if err != nil {
return errors.Wrap(err, "unexpected error reading /proc directory")
}
f, err := n.fileSystem.ReadFile(nginx.PID)
f, err := ioutil.ReadFile(nginx.PID)
if err != nil {
return errors.Wrapf(err, "unexpected error reading %v", nginx.PID)
}

View file

@ -26,7 +26,6 @@ import (
"testing"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/internal/file"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
@ -55,14 +54,10 @@ func TestNginxCheck(t *testing.T) {
defer server.Close()
server.Start()
// mock filesystem
fs := filesystem.DefaultFs{}
n := &NGINXController{
cfg: &Configuration{
ListenPorts: &ngx_config.ListenPorts{},
},
fileSystem: fs,
}
t.Run("no pid or process", func(t *testing.T) {
@ -72,8 +67,8 @@ func TestNginxCheck(t *testing.T) {
})
// create pid file
fs.MkdirAll("/tmp", file.ReadWriteByUser)
pidFile, err := fs.Create(nginx.PID)
os.MkdirAll("/tmp", file.ReadWriteByUser)
pidFile, err := os.Create(nginx.PID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View file

@ -33,8 +33,6 @@ import (
var (
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
EnableSSLChainCompletion = false
// EnableDynamicCertificates Dynamically update SSL certificates instead of reloading NGINX
EnableDynamicCertificates = true
)
const (
@ -75,6 +73,10 @@ const (
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
sslProtocols = "TLSv1.2"
// Disable TLS 1.3 early data
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
sslEarlyData = false
// Time during which a client may reuse the session parameters stored in a cache.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
sslSessionTimeout = "10m"
@ -124,11 +126,6 @@ type Configuration struct {
// By default error logs go to /var/log/nginx/error.log
ErrorLogPath string `json:"error-log-path,omitempty"`
// EnableDynamicTLSRecords enables dynamic TLS record sizes
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
// By default this is enabled
EnableDynamicTLSRecords bool `json:"enable-dynamic-tls-records"`
// EnableModsecurity enables the modsecurity module for NGINX
// By default this is disabled
EnableModsecurity bool `json:"enable-modsecurity"`
@ -317,6 +314,10 @@ type Configuration struct {
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
SSLProtocols string `json:"ssl-protocols,omitempty"`
// Enables or disable TLS 1.3 early data.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
SSLEarlyData bool `json:"ssl-early-data,omitempty"`
// Enables or disables the use of shared SSL cache among worker processes.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
SSLSessionCache bool `json:"ssl-session-cache,omitempty"`
@ -607,6 +608,13 @@ type Configuration struct {
// Block all requests with given Referer headers
BlockReferers []string `json:"block-referers"`
// Lua shared dict configuration data / certificate data
LuaSharedDicts map[string]int `json:"lua-shared-dicts"`
// DefaultSSLCertificate holds the default SSL certificate to use in the configuration
// It can be the fake certificate or the one behind the flag --default-ssl-certificate
DefaultSSLCertificate *ingress.SSLCert `json:"-"`
}
// NewDefault returns the default nginx configuration
@ -640,7 +648,6 @@ func NewDefault() Configuration {
ClientHeaderTimeout: 60,
ClientBodyBufferSize: "8k",
ClientBodyTimeout: 60,
EnableDynamicTLSRecords: true,
EnableUnderscoresInHeaders: false,
ErrorLogLevel: errorLevel,
UseForwardedHeaders: false,
@ -683,6 +690,7 @@ func NewDefault() Configuration {
SSLCiphers: sslCiphers,
SSLECDHCurve: "auto",
SSLProtocols: sslProtocols,
SSLEarlyData: sslEarlyData,
SSLSessionCache: true,
SSLSessionCacheSize: sslSessionCacheSize,
SSLSessionTickets: true,
@ -720,6 +728,7 @@ func NewDefault() Configuration {
LimitRateAfter: 0,
ProxyBuffering: "off",
ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m",
},
UpstreamKeepaliveConnections: 32,
UpstreamKeepaliveTimeout: 60,
@ -767,25 +776,24 @@ func (cfg Configuration) BuildLogFormatUpstream() string {
// TemplateConfig contains the nginx configuration to render the file nginx.conf
type TemplateConfig struct {
ProxySetHeaders map[string]string
AddHeaders map[string]string
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
Cfg Configuration
IsIPV6Enabled bool
IsSSLPassthroughEnabled bool
NginxStatusIpv4Whitelist []string
NginxStatusIpv6Whitelist []string
RedirectServers interface{}
ListenPorts *ListenPorts
PublishService *apiv1.Service
EnableDynamicCertificates bool
EnableMetrics bool
ProxySetHeaders map[string]string
AddHeaders map[string]string
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
Cfg Configuration
IsIPV6Enabled bool
IsSSLPassthroughEnabled bool
NginxStatusIpv4Whitelist []string
NginxStatusIpv6Whitelist []string
RedirectServers interface{}
ListenPorts *ListenPorts
PublishService *apiv1.Service
EnableMetrics bool
PID string
StatusSocket string

View file

@ -167,7 +167,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
}
err := wait.ExponentialBackoff(retry, func() (bool, error) {
err := configureDynamically(pcfg)
err := n.configureDynamically(pcfg)
if err == nil {
klog.V(2).Infof("Dynamic reconfiguration succeeded.")
return true, nil
@ -846,9 +846,9 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *netw
// serviceEndpoints returns the upstream servers (Endpoints) associated with a Service.
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingress.Endpoint, error) {
svc, err := n.store.GetService(svcKey)
var upstreams []ingress.Endpoint
svc, err := n.store.GetService(svcKey)
if err != nil {
return upstreams, err
}
@ -859,14 +859,26 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
if svc.Spec.Type == apiv1.ServiceTypeExternalName {
externalPort, err := strconv.Atoi(backendPort)
if err != nil {
klog.Warningf("Only numeric ports are allowed in ExternalName Services: %q is not a valid port number.", backendPort)
return upstreams, nil
// check if the service ports have one with backendPort as name
found := false
for _, port := range svc.Spec.Ports {
if port.Name == backendPort {
externalPort = int(port.Port)
found = true
break
}
}
if !found {
klog.Errorf("service %v/%v does not have a port with name %v", svc.Namespace, svc.Namespace, backendPort)
return upstreams, nil
}
}
servicePort := apiv1.ServicePort{
Protocol: "TCP",
Port: int32(externalPort),
TargetPort: intstr.FromString(backendPort),
TargetPort: intstr.FromInt(externalPort),
}
endps := getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
if len(endps) == 0 {
@ -897,19 +909,18 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
return upstreams, nil
}
// overridePemFileNameAndPemSHA should only be called when EnableDynamicCertificates
// ideally this function should not exist, the only reason why we use it is that
// we rely on PemFileName in nginx.tmpl to configure SSL directives
// and PemSHA to force reload
func (n *NGINXController) overridePemFileNameAndPemSHA(cert *ingress.SSLCert) {
// TODO(elvinefendi): It is not great but we currently use PemFileName to decide whether SSL needs to be configured
// in nginx configuration or not. The whole thing needs to be refactored, we should rely on a proper
// signal to configure SSL, not PemFileName.
cert.PemFileName = n.cfg.FakeCertificate.PemFileName
func (n *NGINXController) getDefaultSSLCertificate() *ingress.SSLCert {
// read custom default SSL certificate, fall back to generated default certificate
if n.cfg.DefaultSSLCertificate != "" {
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
if err == nil {
return certificate
}
// TODO(elvinefendi): This is again another hacky way of avoiding Nginx reload when certificate
// changes in dynamic SSL mode since FakeCertificate never changes.
cert.PemSHA = n.cfg.FakeCertificate.PemSHA
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
}
return n.cfg.FakeCertificate
}
// createServers builds a map of host name to Server structs from a map of
@ -924,42 +935,28 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
bdef := n.store.GetDefaultBackend()
ngxProxy := proxy.Config{
BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout,
ReadTimeout: bdef.ProxyReadTimeout,
BuffersNumber: bdef.ProxyBuffersNumber,
BufferSize: bdef.ProxyBufferSize,
CookieDomain: bdef.ProxyCookieDomain,
CookiePath: bdef.ProxyCookiePath,
NextUpstream: bdef.ProxyNextUpstream,
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
RequestBuffering: bdef.ProxyRequestBuffering,
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
ProxyBuffering: bdef.ProxyBuffering,
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
}
defaultCertificate := n.cfg.FakeCertificate
// read custom default SSL certificate, fall back to generated default certificate
if n.cfg.DefaultSSLCertificate != "" {
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
if err == nil {
defaultCertificate = certificate
if ngx_config.EnableDynamicCertificates {
n.overridePemFileNameAndPemSHA(defaultCertificate)
}
} else {
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
}
BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout,
ReadTimeout: bdef.ProxyReadTimeout,
BuffersNumber: bdef.ProxyBuffersNumber,
BufferSize: bdef.ProxyBufferSize,
CookieDomain: bdef.ProxyCookieDomain,
CookiePath: bdef.ProxyCookiePath,
NextUpstream: bdef.ProxyNextUpstream,
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
RequestBuffering: bdef.ProxyRequestBuffering,
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
ProxyBuffering: bdef.ProxyBuffering,
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
ProxyMaxTempFileSize: bdef.ProxyMaxTempFileSize,
}
// initialize default server and root location
servers[defServerName] = &ingress.Server{
Hostname: defServerName,
SSLCert: *defaultCertificate,
SSLCert: n.getDefaultSSLCertificate(),
Locations: []*ingress.Location{
{
Path: rootLocation,
@ -1023,6 +1020,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
if host == "" {
host = defServerName
}
if _, ok := servers[host]; ok {
// server already configured
continue
@ -1090,7 +1088,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
// only add a certificate if the server does not have one previously configured
if servers[host].SSLCert.PemFileName != "" {
if servers[host].SSLCert != nil {
continue
}
@ -1100,10 +1098,8 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
tlsSecretName := extractTLSSecretName(host, ing, n.store.GetLocalSSLCert)
if tlsSecretName == "" {
klog.V(3).Infof("Host %q is listed in the TLS section but secretName is empty. Using default certificate.", host)
servers[host].SSLCert = *defaultCertificate
continue
}
@ -1111,7 +1107,6 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
cert, err := n.store.GetLocalSSLCert(secrKey)
if err != nil {
klog.Warningf("Error getting SSL certificate %q: %v. Using default certificate", secrKey, err)
servers[host].SSLCert = *defaultCertificate
continue
}
@ -1126,16 +1121,11 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
klog.Warningf("SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v",
secrKey, host, err)
klog.Warningf("Using default certificate")
servers[host].SSLCert = *defaultCertificate
continue
}
}
if ngx_config.EnableDynamicCertificates {
n.overridePemFileNameAndPemSHA(cert)
}
servers[host].SSLCert = *cert
servers[host].SSLCert = cert
if cert.ExpireTime.Before(time.Now().Add(240 * time.Hour)) {
klog.Warningf("SSL certificate for server %q is about to expire (%v)", host, cert.ExpireTime)
@ -1176,9 +1166,11 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
loc.InfluxDB = anns.InfluxDB
loc.DefaultBackend = anns.DefaultBackend
loc.BackendProtocol = anns.BackendProtocol
loc.FastCGI = anns.FastCGI
loc.CustomHTTPErrors = anns.CustomHTTPErrors
loc.ModSecurity = anns.ModSecurity
loc.Satisfy = anns.Satisfy
loc.Mirror = anns.Mirror
}
// OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into

View file

@ -37,7 +37,6 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations"
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
@ -1109,11 +1108,6 @@ func newNGINXController(t *testing.T) *NGINXController {
t.Fatalf("error creating the configuration map: %v", err)
}
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("error: %v", err)
}
storer := store.New(
ns,
fmt.Sprintf("%v/config", ns),
@ -1122,12 +1116,11 @@ func newNGINXController(t *testing.T) *NGINXController {
"",
10*time.Minute,
clientSet,
fs,
channels.NewRingChannel(10),
pod,
false)
sslCert := ssl.GetFakeSSLCert(fs)
sslCert := ssl.GetFakeSSLCert()
config := &Configuration{
FakeCertificate: sslCert,
ListenPorts: &ngx_config.ListenPorts{
@ -1136,10 +1129,9 @@ func newNGINXController(t *testing.T) *NGINXController {
}
return &NGINXController{
store: storer,
cfg: config,
command: NewNginxCommand(),
fileSystem: fs,
store: storer,
cfg: config,
command: NewNginxCommand(),
}
}

View file

@ -27,6 +27,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
@ -44,7 +45,6 @@ import (
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/filesystem"
adm_controler "k8s.io/ingress-nginx/internal/admission/controller"
"k8s.io/ingress-nginx/internal/file"
@ -69,12 +69,8 @@ const (
tempNginxPattern = "nginx-cfg"
)
var (
tmplPath = "/etc/nginx/template/nginx.tmpl"
)
// NewNGINXController creates a new NGINX Ingress controller.
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *NGINXController {
func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXController {
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
@ -102,8 +98,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
stopLock: &sync.Mutex{},
fileSystem: fs,
runningConfig: new(ingress.Configuration),
Proxy: &TCPProxy{},
@ -135,7 +129,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
config.DefaultSSLCertificate,
config.ResyncPeriod,
config.Client,
fs,
n.updateCh,
pod,
config.DisableCatchAll)
@ -156,7 +149,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
}
onTemplateChange := func() {
template, err := ngx_template.NewTemplate(tmplPath, fs)
template, err := ngx_template.NewTemplate(nginx.TemplatePath)
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
klog.Errorf(`
@ -172,21 +165,16 @@ Error loading new template: %v
n.syncQueue.EnqueueTask(task.GetDummyObject("template-change"))
}
ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs)
ngxTpl, err := ngx_template.NewTemplate(nginx.TemplatePath)
if err != nil {
klog.Fatalf("Invalid NGINX configuration template: %v", err)
}
n.t = ngxTpl
if _, ok := fs.(filesystem.DefaultFs); !ok {
// do not setup watchers on tests
return n
}
_, err = watch.NewFileWatcher(tmplPath, onTemplateChange)
_, err = watch.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
if err != nil {
klog.Fatalf("Error creating file watcher for %v: %v", tmplPath, err)
klog.Fatalf("Error creating file watcher for %v: %v", nginx.TemplatePath, err)
}
filesToWatch := []string{}
@ -260,8 +248,6 @@ type NGINXController struct {
store store.Storer
fileSystem filesystem.Filesystem
metricCollector metric.Collector
validationWebhookServer *http.Server
@ -582,7 +568,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
nsSecName := strings.Replace(secretName, "/", "-", -1)
dh, ok := secret.Data["dhparam.pem"]
if ok {
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
if err != nil {
klog.Warningf("Error adding or updating dhparam file %v: %v", nsSecName, err)
} else {
@ -594,25 +580,26 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
cfg.SSLDHParam = sslDHParam
cfg.DefaultSSLCertificate = n.getDefaultSSLCertificate()
tc := ngx_config.TemplateConfig{
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthroughBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UDPEndpoints,
Cfg: cfg,
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
RedirectServers: buildRedirects(ingressCfg.Servers),
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(),
EnableDynamicCertificates: ngx_config.EnableDynamicCertificates,
EnableMetrics: n.cfg.EnableMetrics,
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthroughBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UDPEndpoints,
Cfg: cfg,
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
RedirectServers: buildRedirects(ingressCfg.Servers),
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(),
EnableMetrics: n.cfg.EnableMetrics,
HealthzURI: nginx.HealthPath,
PID: nginx.PID,
@ -698,7 +685,12 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
if err != nil {
klog.Warningf("Failed to executing diff command: %v", err)
if exitError, ok := err.(*exec.ExitError); ok {
ws := exitError.Sys().(syscall.WaitStatus)
if ws.ExitStatus() == 2 {
klog.Warningf("Failed to executing diff command: %v", err)
}
}
}
klog.Infof("NGINX configuration diff:\n%v", string(diffOutput))
@ -800,7 +792,9 @@ func clearCertificates(config *ingress.Configuration) {
var clearedServers []*ingress.Server
for _, server := range config.Servers {
copyOfServer := *server
copyOfServer.SSLCert = ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
if copyOfServer.SSLCert != nil {
copyOfServer.SSLCert = &ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
}
clearedServers = append(clearedServers, &copyOfServer)
}
config.Servers = clearedServers
@ -848,20 +842,112 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
copyOfRunningConfig.ControllerPodsCount = 0
copyOfPcfg.ControllerPodsCount = 0
if ngx_config.EnableDynamicCertificates {
clearCertificates(&copyOfRunningConfig)
clearCertificates(&copyOfPcfg)
}
clearCertificates(&copyOfRunningConfig)
clearCertificates(&copyOfPcfg)
return copyOfRunningConfig.Equal(&copyOfPcfg)
}
// configureDynamically encodes new Backends in JSON format and POSTs the
// payload to an internal HTTP endpoint handled by Lua.
func configureDynamically(pcfg *ingress.Configuration) error {
backends := make([]*ingress.Backend, len(pcfg.Backends))
func (n *NGINXController) configureDynamically(pcfg *ingress.Configuration) error {
backendsChanged := !reflect.DeepEqual(n.runningConfig.Backends, pcfg.Backends)
if backendsChanged {
err := configureBackends(pcfg.Backends)
if err != nil {
return err
}
}
for i, backend := range pcfg.Backends {
streamConfigurationChanged := !reflect.DeepEqual(n.runningConfig.TCPEndpoints, pcfg.TCPEndpoints) || !reflect.DeepEqual(n.runningConfig.UDPEndpoints, pcfg.UDPEndpoints)
if streamConfigurationChanged {
err := updateStreamConfiguration(pcfg.TCPEndpoints, pcfg.UDPEndpoints)
if err != nil {
return err
}
}
if n.runningConfig.ControllerPodsCount != pcfg.ControllerPodsCount {
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
ControllerPodsCount: pcfg.ControllerPodsCount,
})
if err != nil {
return err
}
if statusCode != http.StatusCreated {
return fmt.Errorf("unexpected error code: %d", statusCode)
}
}
serversChanged := !reflect.DeepEqual(n.runningConfig.Servers, pcfg.Servers)
if serversChanged {
err := configureCertificates(pcfg.Servers)
if err != nil {
return err
}
}
return nil
}
func updateStreamConfiguration(TCPEndpoints []ingress.L4Service, UDPEndpoints []ingress.L4Service) error {
streams := make([]ingress.Backend, 0)
for _, ep := range TCPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
for _, ep := range UDPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
conn, err := net.Dial("unix", nginx.StreamSocket)
if err != nil {
return err
}
defer conn.Close()
buf, err := json.Marshal(streams)
if err != nil {
return err
}
_, err = conn.Write(buf)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "\r\n")
if err != nil {
return err
}
return nil
}
func configureBackends(rawBackends []*ingress.Backend) error {
backends := make([]*ingress.Backend, len(rawBackends))
for i, backend := range rawBackends {
var service *apiv1.Service
if backend.Service != nil {
service = &apiv1.Service{Spec: backend.Service.Spec}
@ -900,99 +986,22 @@ func configureDynamically(pcfg *ingress.Configuration) error {
return fmt.Errorf("unexpected error code: %d", statusCode)
}
streams := make([]ingress.Backend, 0)
for _, ep := range pcfg.TCPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
for _, ep := range pcfg.UDPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
err = updateStreamConfiguration(streams)
if err != nil {
return err
}
statusCode, _, err = nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
ControllerPodsCount: pcfg.ControllerPodsCount,
})
if err != nil {
return err
}
if statusCode != http.StatusCreated {
return fmt.Errorf("unexpected error code: %d", statusCode)
}
if ngx_config.EnableDynamicCertificates {
err = configureCertificates(pcfg)
if err != nil {
return err
}
}
return nil
}
func updateStreamConfiguration(streams []ingress.Backend) error {
conn, err := net.Dial("unix", nginx.StreamSocket)
if err != nil {
return err
}
defer conn.Close()
buf, err := json.Marshal(streams)
if err != nil {
return err
}
_, err = conn.Write(buf)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "\r\n")
if err != nil {
return err
}
return nil
}
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
// that is handled by Lua
func configureCertificates(pcfg *ingress.Configuration) error {
var servers []*ingress.Server
func configureCertificates(rawServers []*ingress.Server) error {
servers := make([]*ingress.Server, 0)
for _, server := range pcfg.Servers {
if server.SSLCert.PemCertKey == "" {
for _, server := range rawServers {
if server.SSLCert == nil {
continue
}
servers = append(servers, &ingress.Server{
Hostname: server.Hostname,
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: server.SSLCert.PemCertKey,
},
})
@ -1000,22 +1009,22 @@ func configureCertificates(pcfg *ingress.Configuration) error {
if server.Alias != "" && ssl.IsValidHostname(server.Alias, server.SSLCert.CN) {
servers = append(servers, &ingress.Server{
Hostname: server.Alias,
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: server.SSLCert.PemCertKey,
},
})
}
}
redirects := buildRedirects(pcfg.Servers)
redirects := buildRedirects(rawServers)
for _, redirect := range redirects {
if redirect.SSLCert.PemCertKey == "" {
if redirect.SSLCert == nil {
continue
}
servers = append(servers, &ingress.Server{
Hostname: redirect.From,
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: redirect.SSLCert.PemCertKey,
},
})
@ -1126,7 +1135,7 @@ func cleanTempNginxCfg() error {
type redirect struct {
From string
To string
SSLCert ingress.SSLCert
SSLCert *ingress.SSLCert
}
func buildRedirects(servers []*ingress.Server) []*redirect {
@ -1170,7 +1179,7 @@ func buildRedirects(servers []*ingress.Server) []*redirect {
To: to,
}
if srv.SSLCert.PemSHA != "" {
if srv.SSLCert != nil {
if ssl.IsValidHostname(from, srv.SSLCert.CN) {
r.SSLCert = srv.SSLCert
} else {

View file

@ -32,14 +32,10 @@ import (
apiv1 "k8s.io/api/core/v1"
"k8s.io/ingress-nginx/internal/ingress"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/nginx"
)
func TestIsDynamicConfigurationEnough(t *testing.T) {
ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
backends := []*ingress.Backend{{
Name: "fakenamespace-myapp-80",
Endpoints: []ingress.Endpoint{
@ -62,7 +58,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
Backend: "fakenamespace-myapp-80",
},
},
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: "fake-certificate",
},
}}
@ -98,8 +94,6 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
Servers: servers,
}
ngx_config.EnableDynamicCertificates = true
if !n.IsDynamicConfigurationEnough(newConfig) {
t.Errorf("Expected to be dynamically configurable when only backends change")
}
@ -112,7 +106,7 @@ func TestIsDynamicConfigurationEnough(t *testing.T) {
Backend: "fakenamespace-myapp-80",
},
},
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: "fake-certificate",
},
}}
@ -168,6 +162,13 @@ func TestConfigureDynamically(t *testing.T) {
defer streamListener.Close()
defer os.Remove(nginx.StreamSocket)
endpointStats := map[string]int{"/configuration/backends": 0, "/configuration/general": 0, "/configuration/servers": 0}
resetEndpointStats := func() {
for k := range endpointStats {
endpointStats[k] = 0
}
}
server := &httptest.Server{
Listener: listener,
Config: &http.Server{
@ -184,6 +185,8 @@ func TestConfigureDynamically(t *testing.T) {
}
body := string(b)
endpointStats[r.URL.Path] += 1
switch r.URL.Path {
case "/configuration/backends":
{
@ -201,6 +204,12 @@ func TestConfigureDynamically(t *testing.T) {
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
}
}
case "/configuration/servers":
{
if !strings.Contains(body, "[]") {
t.Errorf("controllerPodsCount should be present in JSON content: %v", body)
}
}
default:
t.Errorf("unknown request to %s", r.URL.Path)
}
@ -246,17 +255,67 @@ func TestConfigureDynamically(t *testing.T) {
ControllerPodsCount: 2,
}
ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
n := &NGINXController{
runningConfig: &ingress.Configuration{},
cfg: &Configuration{},
}
err = configureDynamically(commonConfig)
err = n.configureDynamically(commonConfig)
if err != nil {
t.Errorf("unexpected error posting dynamic configuration: %v", err)
}
if commonConfig.Backends[0].Endpoints[0].Target != target {
t.Errorf("unexpected change in the configuration object after configureDynamically invocation")
}
for endpoint, count := range endpointStats {
if count != 1 {
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
}
}
resetEndpointStats()
n.runningConfig.Backends = backends
err = n.configureDynamically(commonConfig)
if err != nil {
t.Errorf("unexpected error posting dynamic configuration: %v", err)
}
for endpoint, count := range endpointStats {
if endpoint == "/configuration/backends" {
if count != 0 {
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
}
} else if count != 1 {
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 1, count)
}
}
resetEndpointStats()
n.runningConfig.Servers = servers
err = n.configureDynamically(commonConfig)
if err != nil {
t.Errorf("unexpected error posting dynamic configuration: %v", err)
}
if count, _ := endpointStats["/configuration/backends"]; count != 0 {
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/backends", 0, count)
}
if count, _ := endpointStats["/configuration/servers"]; count != 0 {
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/servers", 0, count)
}
if count, _ := endpointStats["/configuration/general"]; count != 1 {
t.Errorf("Expected %v to receive %d requests but received %d.", "/configuration/general", 0, count)
}
resetEndpointStats()
n.runningConfig.ControllerPodsCount = commonConfig.ControllerPodsCount
err = n.configureDynamically(commonConfig)
if err != nil {
t.Errorf("unexpected error posting dynamic configuration: %v", err)
}
for endpoint, count := range endpointStats {
if count != 0 {
t.Errorf("Expected %v to receive %d requests but received %d.", endpoint, 0, count)
}
}
}
func TestConfigureCertificates(t *testing.T) {
@ -276,7 +335,7 @@ func TestConfigureCertificates(t *testing.T) {
servers := []*ingress.Server{{
Hostname: "myapp.fake",
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
PemCertKey: "fake-cert",
},
}}
@ -316,11 +375,7 @@ func TestConfigureCertificates(t *testing.T) {
defer server.Close()
server.Start()
commonConfig := &ingress.Configuration{
Servers: servers,
}
err = configureCertificates(commonConfig)
err = configureCertificates(servers)
if err != nil {
t.Errorf("unexpected error posting dynamic certificate configuration: %v", err)
}

View file

@ -17,17 +17,20 @@ limitations under the License.
package store
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"strings"
"k8s.io/klog"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/net/ssl"
)
@ -39,7 +42,6 @@ func (s *k8sStore) syncSecret(key string) {
klog.V(3).Infof("Syncing Secret %q", key)
// TODO: getPemCertificate should not write to disk to avoid unnecessary overhead
cert, err := s.getPemCertificate(key)
if err != nil {
if !isErrSecretForAuth(err) {
@ -92,6 +94,7 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
if cert == nil {
return nil, fmt.Errorf("key 'tls.crt' missing from Secret %q", secretName)
}
if key == nil {
return nil, fmt.Errorf("key 'tls.key' missing from Secret %q", secretName)
}
@ -101,15 +104,16 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
}
if !ngx_config.EnableDynamicCertificates || len(ca) > 0 {
err = ssl.StoreSSLCertOnDisk(s.filesystem, nsSecName, sslCert)
if len(ca) > 0 {
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
if err != nil {
return nil, fmt.Errorf("error while storing certificate and key: %v", err)
}
}
if len(ca) > 0 {
err = ssl.ConfigureCACertWithCertAndKey(s.filesystem, nsSecName, ca, sslCert)
sslCert.CAFileName = path
sslCert.CASHA = file.SHA1(path)
err = ssl.ConfigureCACertWithCertAndKey(nsSecName, ca, sslCert)
if err != nil {
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
}
@ -120,14 +124,13 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
msg += " and authentication"
}
klog.V(3).Info(msg)
} else if len(ca) > 0 {
sslCert, err = ssl.CreateCACert(ca)
if err != nil {
return nil, fmt.Errorf("unexpected error creating SSL Cert: %v", err)
}
err = ssl.ConfigureCACert(s.filesystem, nsSecName, ca, sslCert)
err = ssl.ConfigureCACert(nsSecName, ca, sslCert)
if err != nil {
return nil, fmt.Errorf("error configuring CA certificate: %v", err)
}
@ -135,7 +138,6 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
// makes this secret in 'syncSecret' to be used for Certificate Authentication
// this does not enable Certificate Authentication
klog.V(3).Infof("Configuring Secret %q for TLS authentication", secretName)
} else {
if auth != nil {
return nil, ErrSecretForAuth
@ -147,6 +149,21 @@ func (s *k8sStore) getPemCertificate(secretName string) (*ingress.SSLCert, error
sslCert.Name = secret.Name
sslCert.Namespace = secret.Namespace
hasher := sha1.New()
hasher.Write(sslCert.Certificate.Raw)
sslCert.PemSHA = hex.EncodeToString(hasher.Sum(nil))
// the default SSL certificate needs to be present on disk
if secretName == s.defaultSSLCertificate {
path, err := ssl.StoreSSLCertOnDisk(nsSecName, sslCert)
if err != nil {
return nil, errors.Wrap(err, "storing default SSL Certificate")
}
sslCert.PemFileName = path
}
return sslCert, nil
}

View file

@ -152,9 +152,9 @@ func (e NotExistsError) Error() string {
// Run initiates the synchronization of the informers against the API server.
func (i *Informer) Run(stopCh chan struct{}) {
go i.Secret.Run(stopCh)
go i.Endpoint.Run(stopCh)
go i.Service.Run(stopCh)
go i.Secret.Run(stopCh)
go i.ConfigMap.Run(stopCh)
go i.Pod.Run(stopCh)
@ -165,6 +165,7 @@ func (i *Informer) Run(stopCh chan struct{}) {
i.Service.HasSynced,
i.Secret.HasSynced,
i.ConfigMap.HasSynced,
i.Pod.HasSynced,
) {
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
}
@ -208,8 +209,6 @@ type k8sStore struct {
// secret in the annotations.
secretIngressMap ObjectRefMap
filesystem file.Filesystem
// updateCh
updateCh *channels.RingChannel
@ -229,7 +228,6 @@ func New(
namespace, configmap, tcp, udp, defaultSSLCertificate string,
resyncPeriod time.Duration,
client clientset.Interface,
fs file.Filesystem,
updateCh *channels.RingChannel,
pod *k8s.PodInfo,
disableCatchAll bool) Storer {
@ -238,7 +236,6 @@ func New(
informers: &Informer{},
listers: &Lister{},
sslStore: NewSSLCertTracker(),
filesystem: fs,
updateCh: updateCh,
backendConfig: ngx_config.NewDefault(),
syncSecretMu: &sync.Mutex{},
@ -494,6 +491,7 @@ func New(
}
store.syncIngress(ing)
}
updateCh.In() <- Event{
Type: DeleteEvent,
Obj: obj,
@ -814,7 +812,7 @@ func (s *k8sStore) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error
return &resolver.AuthSSLCert{
Secret: name,
CAFileName: cert.CAFileName,
PemSHA: cert.PemSHA,
CASHA: cert.CASHA,
}, nil
}

View file

@ -38,10 +38,8 @@ import (
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/test/e2e/framework"
)
@ -87,7 +85,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -96,7 +93,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -167,7 +163,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -176,7 +171,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -317,7 +311,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -326,7 +319,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -423,7 +415,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -432,7 +423,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -512,7 +502,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -521,7 +510,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -623,7 +611,6 @@ func TestStore(t *testing.T) {
}
}(updateCh)
fs := newFS(t)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
@ -632,7 +619,6 @@ func TestStore(t *testing.T) {
"",
10*time.Minute,
clientSet,
fs,
updateCh,
pod,
false)
@ -701,39 +687,6 @@ func TestStore(t *testing.T) {
if err != nil {
t.Errorf("error creating secret: %v", err)
}
t.Run("should exists a secret in the local store and filesystem", func(t *testing.T) {
ngx_config.EnableDynamicCertificates = false
defer func() { ngx_config.EnableDynamicCertificates = true }()
err := framework.WaitForSecretInNamespace(clientSet, ns, name)
if err != nil {
t.Errorf("error waiting for secret: %v", err)
}
time.Sleep(5 * time.Second)
pemFile := fmt.Sprintf("%v/%v-%v.pem", file.DefaultSSLDirectory, ns, name)
err = framework.WaitForFileInFS(pemFile, fs)
if err != nil {
t.Errorf("error waiting for file to exist on the file system: %v", err)
}
secretName := fmt.Sprintf("%v/%v", ns, name)
sslCert, err := storer.GetLocalSSLCert(secretName)
if err != nil {
t.Errorf("error reading local secret %v: %v", secretName, err)
}
if sslCert == nil {
t.Errorf("expected a secret but none returned")
}
pemSHA := file.SHA1(pemFile)
if sslCert.PemSHA != pemSHA {
t.Errorf("SHA of secret on disk differs from local secret store (%v != %v)", pemSHA, sslCert.PemSHA)
}
})
})
// test add ingress with secret it doesn't exists and then add secret
@ -821,22 +774,9 @@ func deleteIngress(ingress *networking.Ingress, clientSet kubernetes.Interface,
t.Logf("Ingress %+v deleted", ingress)
}
func newFS(t *testing.T) file.Filesystem {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("error creating filesystem: %v", err)
}
return fs
}
// newStore creates a new mock object store for tests which do not require the
// use of Informers.
func newStore(t *testing.T) *k8sStore {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("error: %v", err)
}
pod := &k8s.PodInfo{
Name: "ingress-1",
Namespace: v1.NamespaceDefault,
@ -853,7 +793,6 @@ func newStore(t *testing.T) *k8sStore {
Pod: PodLister{cache.NewStore(cache.MetaNamespaceKeyFunc)},
},
sslStore: NewSSLCertTracker(),
filesystem: fs,
updateCh: channels.NewRingChannel(10),
syncSecretMu: new(sync.Mutex),
backendConfigMu: new(sync.RWMutex),

View file

@ -59,10 +59,23 @@ const (
globalAuthSnippet = "global-auth-snippet"
globalAuthCacheKey = "global-auth-cache-key"
globalAuthCacheDuration = "global-auth-cache-duration"
luaSharedDictsKey = "lua-shared-dicts"
)
var (
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
defaultLuaSharedDicts = map[string]int{
"configuration_data": 20,
"certificate_data": 20,
"balancer_ewma": 10,
"balancer_ewma_last_touched_at": 10,
"balancer_ewma_locks": 1,
}
)
const (
maxAllowedLuaDictSize = 200
maxNumberOfLuaDicts = 100
)
// ReadConfig obtains the configuration defined by the user merged with the defaults.
@ -87,7 +100,40 @@ func ReadConfig(src map[string]string) config.Configuration {
blockUserAgentList := make([]string, 0)
blockRefererList := make([]string, 0)
responseHeaders := make([]string, 0)
luaSharedDicts := make(map[string]int)
//parse lua shared dict values
if val, ok := conf[luaSharedDictsKey]; ok {
delete(conf, luaSharedDictsKey)
lsd := strings.Split(val, ",")
for _, v := range lsd {
v = strings.Replace(v, " ", "", -1)
results := strings.SplitN(v, ":", 2)
dictName := results[0]
size, err := strconv.Atoi(results[1])
if err != nil {
klog.Errorf("Ignoring non integer value %v for Lua dictionary %v: %v.", results[1], dictName, err)
continue
}
if size > maxAllowedLuaDictSize {
klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %v.", size, dictName, maxAllowedLuaDictSize)
continue
}
if len(luaSharedDicts)+1 > maxNumberOfLuaDicts {
klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.",
size, dictName, maxNumberOfLuaDicts)
continue
}
luaSharedDicts[dictName] = size
}
}
// set default Lua shared dicts
for k, v := range defaultLuaSharedDicts {
if _, ok := luaSharedDicts[k]; !ok {
luaSharedDicts[k] = v
}
}
if val, ok := conf[customHTTPErrors]; ok {
delete(conf, customHTTPErrors)
for _, i := range strings.Split(val, ",") {
@ -305,6 +351,7 @@ func ReadConfig(src map[string]string) config.Configuration {
to.HideHeaders = hideHeadersList
to.ProxyStreamResponses = streamResponses
to.DisableIpv6DNS = !ing_net.IsIPv6Enabled()
to.LuaSharedDicts = luaSharedDicts
config := &mapstructure.DecoderConfig{
Metadata: nil,

View file

@ -64,7 +64,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
"access-log-path": "/var/log/test/access.log",
"error-log-path": "/var/log/test/error.log",
"use-gzip": "true",
"enable-dynamic-tls-records": "false",
"gzip-level": "9",
"gzip-types": "text/html",
"proxy-real-ip-cidr": "1.1.1.1/8,2.2.2.2/24",
@ -84,7 +83,6 @@ func TestMergeConfigMapToStruct(t *testing.T) {
def.SkipAccessLogURLs = []string{"/log", "/demo", "/test"}
def.ProxyReadTimeout = 1
def.ProxySendTimeout = 2
def.EnableDynamicTLSRecords = false
def.UseProxyProtocol = true
def.GzipLevel = 9
def.GzipTypes = "text/html"
@ -95,7 +93,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
def.NginxStatusIpv4Whitelist = []string{"127.0.0.1", "10.0.0.0/24"}
def.NginxStatusIpv6Whitelist = []string{"::1", "2001::/16"}
def.ProxyAddOriginalURIHeader = false
def.LuaSharedDicts = defaultLuaSharedDicts
def.DisableIpv6DNS = true
hash, err := hashstructure.Hash(def, &hashstructure.HashOptions{
@ -125,6 +123,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
}
def = config.NewDefault()
def.LuaSharedDicts = defaultLuaSharedDicts
def.DisableIpv6DNS = true
hash, err = hashstructure.Hash(def, &hashstructure.HashOptions{
@ -143,6 +142,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
}
def = config.NewDefault()
def.LuaSharedDicts = defaultLuaSharedDicts
def.WhitelistSourceRange = []string{"1.1.1.1/32"}
def.DisableIpv6DNS = true
@ -303,3 +303,56 @@ func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) {
}
}
}
func TestLuaSharedDictsParsing(t *testing.T) {
testsCases := []struct {
name string
entry map[string]string
expect map[string]int
}{
{
name: "default dicts configured when lua-shared-dicts is not set",
entry: make(map[string]string),
expect: defaultLuaSharedDicts,
},
{
name: "configuration_data only",
entry: map[string]string{"lua-shared-dicts": "configuration_data:5"},
expect: map[string]int{"configuration_data": 5},
},
{
name: "certificate_data only",
entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"},
expect: map[string]int{"certificate_data": 4},
},
{
name: "custom dicts",
entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"},
expect: map[string]int{"configuration_data": 10, "my_random_dict": 15, "another_example": 2},
},
{
name: "invalid size value should be ignored",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a"},
expect: map[string]int{"mydict": 10},
},
{
name: "dictionary size can not be larger than 200",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201"},
expect: map[string]int{"mydict": 10},
},
}
for _, tc := range testsCases {
// dynamically insert default dicts in the expected output
for dictName, dictSize := range defaultLuaSharedDicts {
if _, ok := tc.expect[dictName]; !ok {
tc.expect[dictName] = dictSize
}
}
cfg := ReadConfig(tc.entry)
if !reflect.DeepEqual(cfg.LuaSharedDicts, tc.expect) {
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, cfg.LuaSharedDicts)
}
}
}

View file

@ -21,6 +21,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/url"
@ -37,13 +38,13 @@ import (
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/controller/config"
ing_net "k8s.io/ingress-nginx/internal/net"
"k8s.io/klog"
)
const (
@ -66,8 +67,8 @@ type Template struct {
//NewTemplate returns a new Template instance or an
//error if the specified template file contains errors
func NewTemplate(file string, fs file.Filesystem) (*Template, error) {
data, err := fs.ReadFile(file)
func NewTemplate(file string) (*Template, error) {
data, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.Wrapf(err, "unexpected error reading template %v", file)
}
@ -132,36 +133,37 @@ var (
}
return true
},
"escapeLiteralDollar": escapeLiteralDollar,
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
"buildAuthResponseHeaders": buildAuthResponseHeaders,
"buildProxyPass": buildProxyPass,
"filterRateLimits": filterRateLimits,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"buildResolversForLua": buildResolversForLua,
"configForLua": configForLua,
"locationConfigForLua": locationConfigForLua,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationInLocationList": isLocationInLocationList,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
"getenv": os.Getenv,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimSpace": strings.TrimSpace,
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"formatIP": formatIP,
"buildNextUpstream": buildNextUpstream,
"getIngressInformation": getIngressInformation,
"escapeLiteralDollar": escapeLiteralDollar,
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
"buildAuthResponseHeaders": buildAuthResponseHeaders,
"buildProxyPass": buildProxyPass,
"filterRateLimits": filterRateLimits,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"configForLua": configForLua,
"locationConfigForLua": locationConfigForLua,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationInLocationList": isLocationInLocationList,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
"getenv": os.Getenv,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimSpace": strings.TrimSpace,
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"formatIP": formatIP,
"quote": quote,
"buildNextUpstream": buildNextUpstream,
"getIngressInformation": getIngressInformation,
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
return struct{ First, Second interface{} }{all, server}
},
@ -177,6 +179,8 @@ var (
"opentracingPropagateContext": opentracingPropagateContext,
"buildCustomErrorLocationsPerServer": buildCustomErrorLocationsPerServer,
"shouldLoadModSecurityModule": shouldLoadModSecurityModule,
"buildHTTPListener": buildHTTPListener,
"buildHTTPSListener": buildHTTPSListener,
}
)
@ -208,6 +212,21 @@ func formatIP(input string) string {
return fmt.Sprintf("[%s]", input)
}
func quote(input interface{}) string {
var inputStr string
switch input := input.(type) {
case string:
inputStr = input
break
case fmt.Stringer:
inputStr = input.String()
break
default:
inputStr = fmt.Sprintf("%v", input)
}
return fmt.Sprintf("%q", inputStr)
}
func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
if !disableLuaRestyWAF && len(mode) > 0 {
return true
@ -216,19 +235,26 @@ func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {
return false
}
func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF bool) string {
var out []string
cfg, ok := c.(config.Configuration)
if !ok {
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
return ""
}
servers, ok := s.([]*ingress.Server)
if !ok {
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
return ""
}
out := []string{
"lua_shared_dict configuration_data 15M",
"lua_shared_dict certificate_data 16M",
for name, size := range cfg.LuaSharedDicts {
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
}
if !disableLuaRestyWAF {
// TODO: there must be a better place for this
if _, ok := cfg.LuaSharedDicts["waf_storage"]; !ok && !disableLuaRestyWAF {
luaRestyWAFEnabled := func() bool {
for _, server := range servers {
for _, location := range server.Locations {
@ -244,38 +270,23 @@ func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string {
}
}
return strings.Join(out, ";\n\r") + ";"
return strings.Join(out, ";\n") + ";\n"
}
func buildResolversForLua(res interface{}, disableIpv6 interface{}) string {
nss, ok := res.([]net.IP)
func luaConfigurationRequestBodySize(c interface{}) string {
cfg, ok := c.(config.Configuration)
if !ok {
klog.Errorf("expected a '[]net.IP' type but %T was returned", res)
return ""
}
no6, ok := disableIpv6.(bool)
if !ok {
klog.Errorf("expected a 'bool' type but %T was returned", disableIpv6)
return ""
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
return "100" // just a default number
}
if len(nss) == 0 {
return ""
size := cfg.LuaSharedDicts["configuration_data"]
if size < cfg.LuaSharedDicts["certificate_data"] {
size = cfg.LuaSharedDicts["certificate_data"]
}
size = size + 1
r := []string{}
for _, ns := range nss {
if ing_net.IsIPV6(ns) {
if no6 {
continue
}
r = append(r, fmt.Sprintf("\"[%v]\"", ns))
} else {
r = append(r, fmt.Sprintf("\"%v\"", ns))
}
}
return strings.Join(r, ", ")
return fmt.Sprintf("%d", size)
}
// configForLua returns some general configuration as Lua table represented as string
@ -314,7 +325,7 @@ func locationConfigForLua(l interface{}, s interface{}, a interface{}) string {
return "{}"
}
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (len(server.SSLCert.PemFileName) > 0 && location.Rewrite.SSLRedirect)
forceSSLRedirect := location.Rewrite.ForceSSLRedirect || (server.SSLCert != nil && location.Rewrite.SSLRedirect)
forceSSLRedirect = forceSSLRedirect && !isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations)
return fmt.Sprintf(`{
@ -497,6 +508,9 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
case "AJP":
proto = ""
proxyPass = "ajp_pass"
case "FCGI":
proto = ""
proxyPass = "fastcgi_pass"
}
upstreamName := "upstream_balancer"
@ -1088,3 +1102,169 @@ func shouldLoadModSecurityModule(c interface{}, s interface{}) bool {
// Not enabled globally nor via annotation on a location, no need to load the module.
return false
}
func buildHTTPListener(t interface{}, s interface{}) string {
var out []string
tc, ok := t.(config.TemplateConfig)
if !ok {
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
return ""
}
hostname, ok := s.(string)
if !ok {
klog.Errorf("expected a 'string' type but %T was returned", s)
return ""
}
addrV4 := []string{""}
if len(tc.Cfg.BindAddressIpv4) > 0 {
addrV4 = tc.Cfg.BindAddressIpv4
}
co := commonListenOptions(tc, hostname)
out = append(out, httpListener(addrV4, co, tc)...)
if !tc.IsIPV6Enabled {
return strings.Join(out, "\n")
}
addrV6 := []string{"[::]"}
if len(tc.Cfg.BindAddressIpv6) > 0 {
addrV6 = tc.Cfg.BindAddressIpv6
}
out = append(out, httpListener(addrV6, co, tc)...)
return strings.Join(out, "\n")
}
func buildHTTPSListener(t interface{}, s interface{}) string {
var out []string
tc, ok := t.(config.TemplateConfig)
if !ok {
klog.Errorf("expected a 'config.TemplateConfig' type but %T was returned", t)
return ""
}
hostname, ok := s.(string)
if !ok {
klog.Errorf("expected a 'string' type but %T was returned", s)
return ""
}
/*
if server.SSLCert == nil && server.Hostname != "_" {
return ""
}
*/
co := commonListenOptions(tc, hostname)
addrV4 := []string{""}
if len(tc.Cfg.BindAddressIpv4) > 0 {
addrV4 = tc.Cfg.BindAddressIpv4
}
out = append(out, httpsListener(addrV4, co, tc)...)
if !tc.IsIPV6Enabled {
return strings.Join(out, "\n")
}
addrV6 := []string{"[::]"}
if len(tc.Cfg.BindAddressIpv6) > 0 {
addrV6 = tc.Cfg.BindAddressIpv6
}
out = append(out, httpsListener(addrV6, co, tc)...)
return strings.Join(out, "\n")
}
func commonListenOptions(template config.TemplateConfig, hostname string) string {
var out []string
if template.Cfg.UseProxyProtocol {
out = append(out, "proxy_protocol")
}
if hostname != "_" {
return strings.Join(out, " ")
}
// setup options that are valid only once per port
out = append(out, "default_server")
if template.Cfg.ReusePort {
out = append(out, "reuseport")
}
out = append(out, fmt.Sprintf("backlog=%v", template.BacklogSize))
return strings.Join(out, " ")
}
func httpListener(addresses []string, co string, tc config.TemplateConfig) []string {
out := make([]string, 0)
for _, address := range addresses {
l := make([]string, 0)
l = append(l, "listen")
if address == "" {
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTP))
} else {
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTP))
}
l = append(l, co)
l = append(l, ";")
out = append(out, strings.Join(l, " "))
}
return out
}
func httpsListener(addresses []string, co string, tc config.TemplateConfig) []string {
out := make([]string, 0)
for _, address := range addresses {
l := make([]string, 0)
l = append(l, "listen")
if tc.IsSSLPassthroughEnabled {
if address == "" {
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.SSLProxy))
} else {
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.SSLProxy))
}
l = append(l, "proxy_protocol")
} else {
if address == "" {
l = append(l, fmt.Sprintf("%v", tc.ListenPorts.HTTPS))
} else {
l = append(l, fmt.Sprintf("%v:%v", address, tc.ListenPorts.HTTPS))
}
if tc.Cfg.UseProxyProtocol {
l = append(l, "proxy_protocol")
}
}
l = append(l, co)
l = append(l, "ssl")
if tc.Cfg.UseHTTP2 {
l = append(l, "http2")
}
l = append(l, ";")
out = append(out, strings.Join(l, " "))
}
return out
}

View file

@ -17,20 +17,20 @@ limitations under the License.
package template
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"path/filepath"
"reflect"
"strings"
"testing"
"encoding/base64"
"fmt"
jsoniter "github.com/json-iterator/go"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/authreq"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
@ -39,8 +39,18 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/internal/ingress/annotations/rewrite"
"k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/nginx"
)
func init() {
// the default value of nginx.TemplatePath assumes the template exists in
// the root filesystem and not in the rootfs directory
path, err := filepath.Abs(filepath.Join("../../../../rootfs/", nginx.TemplatePath))
if err == nil {
nginx.TemplatePath = path
}
}
var (
// TODO: add tests for SSLPassthrough
tmplFuncTestcases = map[string]struct {
@ -168,7 +178,14 @@ proxy_pass http://upstream_balancer;`,
func TestBuildLuaSharedDictionaries(t *testing.T) {
invalidType := &ingress.Ingress{}
expected := ""
actual := buildLuaSharedDictionaries(invalidType, true)
// config lua dict
cfg := config.Configuration{
LuaSharedDicts: map[string]int{
"configuration_data": 10, "certificate_data": 20,
},
}
actual := buildLuaSharedDictionaries(cfg, invalidType, true)
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
@ -184,20 +201,41 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}},
},
}
configuration := buildLuaSharedDictionaries(servers, false)
if !strings.Contains(configuration, "lua_shared_dict configuration_data") {
// returns value from config
configuration := buildLuaSharedDictionaries(cfg, servers, false)
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
}
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
}
if strings.Contains(configuration, "waf_storage") {
t.Errorf("expected to not include 'waf_storage' but got %s", configuration)
}
servers[1].Locations[0].LuaRestyWAF = luarestywaf.Config{Mode: "ACTIVE"}
configuration = buildLuaSharedDictionaries(servers, false)
configuration = buildLuaSharedDictionaries(cfg, servers, false)
if !strings.Contains(configuration, "lua_shared_dict waf_storage") {
t.Errorf("expected to configure 'waf_storage', but got %s", configuration)
}
// test invalid config
configuration = buildLuaSharedDictionaries(invalidType, servers, false)
if expected != actual {
t.Errorf("Expected '%v' but returned '%v' ", expected, actual)
}
}
func TestLuaConfigurationRequestBodySize(t *testing.T) {
cfg := config.Configuration{
LuaSharedDicts: map[string]int{
"configuration_data": 10, "certificate_data": 20,
},
}
size := luaConfigurationRequestBodySize(cfg)
if "21" != size {
t.Errorf("expected the size to be 20 but got: %v", size)
}
}
func TestFormatIP(t *testing.T) {
@ -219,6 +257,21 @@ func TestFormatIP(t *testing.T) {
}
}
func TestQuote(t *testing.T) {
cases := map[interface{}]string{
"foo": `"foo"`,
"\"foo\"": `"\"foo\""`,
"foo\nbar": `"foo\nbar"`,
10: `"10"`,
}
for input, output := range cases {
actual := quote(input)
if actual != output {
t.Errorf("quote('%s'): expected '%v' but returned '%v'", input, output, actual)
}
}
}
func TestBuildLocation(t *testing.T) {
invalidType := &ingress.Ingress{}
expected := "/"
@ -408,16 +461,13 @@ func TestTemplateWithData(t *testing.T) {
dat.ListenPorts = &config.ListenPorts{}
}
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
ngxTpl, err := NewTemplate(nginx.TemplatePath)
if err != nil {
t.Errorf("invalid NGINX template: %v", err)
}
dat.Cfg.DefaultSSLCertificate = &ingress.SSLCert{}
rt, err := ngxTpl.Write(dat)
if err != nil {
t.Errorf("invalid NGINX template: %v", err)
@ -452,12 +502,7 @@ func BenchmarkTemplateWithData(b *testing.B) {
b.Errorf("unexpected error unmarshalling json: %v", err)
}
fs, err := file.NewFakeFS()
if err != nil {
b.Fatalf("unexpected error: %v", err)
}
ngxTpl, err := NewTemplate("/etc/nginx/template/nginx.tmpl", fs)
ngxTpl, err := NewTemplate(nginx.TemplatePath)
if err != nil {
b.Errorf("invalid NGINX template: %v", err)
}
@ -550,43 +595,6 @@ func TestBuildForwardedFor(t *testing.T) {
}
}
func TestBuildResolversForLua(t *testing.T) {
ipOne := net.ParseIP("192.0.0.1")
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
ipList := []net.IP{ipOne, ipTwo}
invalidType := &ingress.Ingress{}
expected := ""
actual := buildResolversForLua(invalidType, false)
// Invalid Type for []net.IP
if expected != actual {
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
}
actual = buildResolversForLua(ipList, invalidType)
// Invalid Type for bool
if expected != actual {
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
}
expected = "\"192.0.0.1\", \"[2001:db8:1234::]\""
actual = buildResolversForLua(ipList, false)
if expected != actual {
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
}
expected = "\"192.0.0.1\""
actual = buildResolversForLua(ipList, true)
if expected != actual {
t.Errorf("Expected '%v' but returned '%v'", expected, actual)
}
}
func TestBuildResolvers(t *testing.T) {
ipOne := net.ParseIP("192.0.0.1")
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
@ -874,6 +882,7 @@ func TestOpentracingPropagateContext(t *testing.T) {
&ingress.Location{BackendProtocol: "GRPC"}: "opentracing_grpc_propagate_context",
&ingress.Location{BackendProtocol: "GRPCS"}: "opentracing_grpc_propagate_context",
&ingress.Location{BackendProtocol: "AJP"}: "opentracing_propagate_context",
&ingress.Location{BackendProtocol: "FCGI"}: "opentracing_propagate_context",
"not a location": "opentracing_propagate_context",
}

View file

@ -154,4 +154,8 @@ type Backend struct {
// Modifies the HTTP version the proxy uses to interact with the backend.
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
ProxyHTTPVersion string `json:"proxy-http-version"`
// Sets the maximum temp file size when proxy-buffers capacity is exceeded.
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_max_temp_file_size
ProxyMaxTempFileSize string `json:"proxy-max-temp-file-size"`
}

View file

@ -228,7 +228,7 @@ func (cm Controller) Collect(ch chan<- prometheus.Metric) {
// SetSSLExpireTime sets the expiration time of SSL Certificates
func (cm *Controller) SetSSLExpireTime(servers []*ingress.Server) {
for _, s := range servers {
if s.Hostname != "" && s.SSLCert.ExpireTime.Unix() > 0 {
if s.Hostname != "" && s.SSLCert != nil && s.SSLCert.ExpireTime.Unix() > 0 {
labels := make(prometheus.Labels, len(cm.labels)+1)
for k, v := range cm.labels {
labels[k] = v

View file

@ -80,13 +80,13 @@ func TestControllerCounters(t *testing.T) {
servers := []*ingress.Server{
{
Hostname: "demo",
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
ExpireTime: t1,
},
},
{
Hostname: "invalid",
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
ExpireTime: time.Unix(0, 0),
},
},
@ -135,13 +135,13 @@ func TestRemoveMetrics(t *testing.T) {
servers := []*ingress.Server{
{
Hostname: "demo",
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
ExpireTime: t1,
},
},
{
Hostname: "invalid",
SSLCert: ingress.SSLCert{
SSLCert: &ingress.SSLCert{
ExpireTime: time.Unix(0, 0),
},
},

View file

@ -26,6 +26,9 @@ type Resolver interface {
// GetDefaultBackend returns the backend that must be used as default
GetDefaultBackend() defaults.Backend
// GetConfigMap searches for configmap containing the namespace and name usting the character /
GetConfigMap(string) (*apiv1.ConfigMap, error)
// GetSecret searches for secrets containing the namespace and name using a the character /
GetSecret(string) (*apiv1.Secret, error)
@ -48,8 +51,8 @@ type AuthSSLCert struct {
Secret string `json:"secret"`
// CAFileName contains the path to the secrets 'ca.crt'
CAFileName string `json:"caFilename"`
// PemSHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
PemSHA string `json:"pemSha"`
// CASHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
CASHA string `json:"caSha"`
}
// Equal tests for equality between two AuthSSLCert types
@ -67,7 +70,7 @@ func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
if asslc1.CAFileName != assl2.CAFileName {
return false
}
if asslc1.PemSHA != assl2.PemSHA {
if asslc1.CASHA != assl2.CASHA {
return false
}

View file

@ -31,6 +31,11 @@ func (m Mock) GetDefaultBackend() defaults.Backend {
return defaults.Backend{}
}
// GetConfigMap searches for configmap containing the namespace and name usting the character /
func (m Mock) GetConfigMap(string) (*apiv1.ConfigMap, error) {
return nil, nil
}
// GetSecret searches for secrets contenating the namespace and name using a the character /
func (m Mock) GetSecret(string) (*apiv1.Secret, error) {
return nil, nil

View file

@ -20,25 +20,36 @@ import (
"crypto/x509"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// SSLCert describes a SSL certificate to be used in a server
type SSLCert struct {
metav1.ObjectMeta `json:"metadata,omitempty"`
Certificate *x509.Certificate `json:"certificate,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Certificate *x509.Certificate `json:"-"`
// CAFileName contains the path to the file with the root certificate
CAFileName string `json:"caFileName"`
// CASHA contains the sha1 of the ca file.
// This is used to detect changes in the secret that contains certificates
CASHA string `json:"caSha"`
// PemFileName contains the path to the file with the certificate and key concatenated
PemFileName string `json:"pemFileName"`
// PemSHA contains the sha1 of the pem file.
// This is used to detect changes in the secret that contains the certificates
// This is used to detect changes in the secret that contains certificates
PemSHA string `json:"pemSha"`
// CN contains all the common names defined in the SSL certificate
CN []string `json:"cn"`
// ExpiresTime contains the expiration of this SSL certificate in timestamp format
ExpireTime time.Time `json:"expires"`
// Pem encoded certificate and key concatenated
PemCertKey string `json:"pemCertKey,omitempty"`
}
@ -50,5 +61,5 @@ func (s SSLCert) GetObjectKind() schema.ObjectKind {
// HashInclude defines if a field should be used or not to calculate the hash
func (s SSLCert) HashInclude(field string, v interface{}) (bool, error) {
return (field != "PemSHA" && field != "ExpireTime"), nil
return (field != "PemSHA" && field != "CASHA" && field != "ExpireTime"), nil
}

View file

@ -1,38 +0,0 @@
/*
Copyright 2017 The Kubernetes 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.
*/
package ingress
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGetObjectKindForSSLCert(t *testing.T) {
fk := &SSLCert{
ObjectMeta: metav1.ObjectMeta{},
CAFileName: "ca_file",
PemFileName: "pemfile",
PemSHA: "pem_sha",
CN: []string{},
}
r := fk.GetObjectKind()
if r == nil {
t.Errorf("Returned nil but expected a valid ObjectKind")
}
}

View file

@ -27,10 +27,12 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/authtls"
"k8s.io/ingress-nginx/internal/ingress/annotations/connection"
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
@ -179,7 +181,7 @@ type Server struct {
// the server or in the remote endpoint
SSLPassthrough bool `json:"sslPassthrough"`
// SSLCert describes the certificate that will be used on the server
SSLCert SSLCert `json:"sslCert"`
SSLCert *SSLCert `json:"sslCert"`
// Locations list of URIs configured in the server.
Locations []*Location `json:"locations,omitempty"`
// Alias return the alias of the server name
@ -230,7 +232,7 @@ type Location struct {
// Backend describes the name of the backend to use.
Backend string `json:"backend"`
// Service describes the referenced services from the ingress
Service *apiv1.Service `json:"service,omitempty"`
Service *apiv1.Service `json:"-"`
// Port describes to which port from the service
Port intstr.IntOrString `json:"port"`
// Overwrite the Host header passed into the backend. Defaults to
@ -297,7 +299,7 @@ type Location struct {
ClientBodyBufferSize string `json:"clientBodyBufferSize,omitempty"`
// DefaultBackend allows the use of a custom default backend for this location.
// +optional
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
DefaultBackend *apiv1.Service `json:"-"`
// DefaultBackendUpstreamName is the upstream-formatted string for the name of
// this location's custom default backend
DefaultBackendUpstreamName string `json:"defaultBackendUpstreamName,omitempty"`
@ -316,6 +318,9 @@ type Location struct {
// BackendProtocol indicates which protocol should be used to communicate with the service
// By default this is HTTP
BackendProtocol string `json:"backend-protocol"`
// FastCGI allows the ingress to act as a FastCGI client for a given location.
// +optional
FastCGI fastcgi.Config `json:"fastcgi,omitempty"`
// CustomHTTPErrors specifies the error codes that should be intercepted.
// +optional
CustomHTTPErrors []int `json:"custom-http-errors"`
@ -324,6 +329,9 @@ type Location struct {
ModSecurity modsecurity.Config `json:"modsecurity"`
// Satisfy dictates allow access if any or all is set
Satisfy string `json:"satisfy"`
// Mirror allows you to mirror traffic to a "test" backend
// +optional
Mirror mirror.Config `json:"mirror,omitempty"`
}
// SSLPassthroughBackend describes a SSL upstream server configured
@ -331,7 +339,7 @@ type Location struct {
// The endpoints must provide the TLS termination exposing the required SSL certificate.
// The ingress controller only pipes the underlying TCP connection
type SSLPassthroughBackend struct {
Service *apiv1.Service `json:"service,omitempty"`
Service *apiv1.Service `json:"-"`
Port intstr.IntOrString `json:"port"`
// Backend describes the endpoints to use.
Backend string `json:"namespace,omitempty"`
@ -348,7 +356,7 @@ type L4Service struct {
// Endpoints active endpoints of the service
Endpoints []Endpoint `json:"endpoints,omitempty"`
// k8s Service
Service *apiv1.Service `json:"service,omitempty"`
Service *apiv1.Service `json:"-"`
}
// L4Backend describes the kubernetes service behind L4 Ingress service
@ -369,8 +377,8 @@ type ProxyProtocol struct {
// Ingress holds the definition of an Ingress plus its annotations
type Ingress struct {
networking.Ingress
ParsedAnnotations *annotations.Ingress
networking.Ingress `json:"-"`
ParsedAnnotations *annotations.Ingress `json:"parsedAnnotations"`
}
// GeneralConfig holds the definition of lua general configuration data

View file

@ -266,7 +266,7 @@ func (s1 *Server) Equal(s2 *Server) bool {
if s1.SSLPassthrough != s2.SSLPassthrough {
return false
}
if !(&s1.SSLCert).Equal(&s2.SSLCert) {
if !(s1.SSLCert).Equal(s2.SSLCert) {
return false
}
if s1.Alias != s2.Alias {
@ -401,6 +401,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
return false
}
if !(&l1.FastCGI).Equal(&l2.FastCGI) {
return false
}
match := compareInts(l1.CustomHTTPErrors, l2.CustomHTTPErrors)
if !match {
return false
@ -418,6 +422,14 @@ func (l1 *Location) Equal(l2 *Location) bool {
return false
}
if l1.Mirror.URI != l2.Mirror.URI {
return false
}
if l1.Mirror.RequestBody != l2.Mirror.RequestBody {
return false
}
return true
}
@ -504,7 +516,7 @@ func (s1 *SSLCert) Equal(s2 *SSLCert) bool {
if s1 == nil || s2 == nil {
return false
}
if s1.PemFileName != s2.PemFileName {
if s1.CASHA != s2.CASHA {
return false
}
if s1.PemSHA != s2.PemSHA {

View file

@ -122,13 +122,13 @@ func NetworkingIngressAvailable(client clientset.Interface) bool {
return false
}
serverVersion, _ := client.Discovery().ServerVersion()
serverVersion, err := client.Discovery().ServerVersion()
if err != nil {
klog.Errorf("unexpected error parsing Kubernetes version: %v", err)
return false
}
runningVersion, _ := version.ParseGeneric(serverVersion.String())
runningVersion, err := version.ParseGeneric(serverVersion.String())
if err != nil {
klog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
return false

View file

@ -27,8 +27,10 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"strconv"
"strings"
"sync"
@ -51,6 +53,22 @@ const (
fakeCertificateName = "default-fake-certificate"
)
func init() {
_, err := os.Stat(file.DefaultSSLDirectory)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(file.DefaultSSLDirectory, file.ReadWriteByUser)
if err != nil {
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
}
return
}
klog.Fatalf("Unexpected error checking for default SSL directory: %v", err)
}
}
// getPemFileName returns absolute file path and file name of pem cert related to given fullSecretName
func getPemFileName(fullSecretName string) (string, string) {
pemName := fmt.Sprintf("%v.pem", fullSecretName)
@ -164,88 +182,58 @@ func CreateCACert(ca []byte) (*ingress.SSLCert, error) {
// StoreSSLCertOnDisk creates a .pem file with content PemCertKey from the given sslCert
// and sets relevant remaining fields of sslCert object
func StoreSSLCertOnDisk(fs file.Filesystem, name string, sslCert *ingress.SSLCert) error {
func StoreSSLCertOnDisk(name string, sslCert *ingress.SSLCert) (string, error) {
pemFileName, _ := getPemFileName(name)
pemFile, err := fs.Create(pemFileName)
err := ioutil.WriteFile(pemFileName, []byte(sslCert.PemCertKey), file.ReadWriteByUser)
if err != nil {
return fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
}
defer pemFile.Close()
_, err = pemFile.Write([]byte(sslCert.PemCertKey))
if err != nil {
return fmt.Errorf("could not write data to PEM file %v: %v", pemFileName, err)
return "", fmt.Errorf("could not create PEM certificate file %v: %v", pemFileName, err)
}
sslCert.PemFileName = pemFileName
sslCert.PemSHA = file.SHA1(pemFileName)
return nil
return pemFileName, nil
}
// ConfigureCACertWithCertAndKey appends ca into existing PEM file consisting of cert and key
// and sets relevant fields in sslCert object
func ConfigureCACertWithCertAndKey(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
func ConfigureCACertWithCertAndKey(name string, ca []byte, sslCert *ingress.SSLCert) error {
err := verifyPemCertAgainstRootCA(sslCert.Certificate, ca)
if err != nil {
oe := fmt.Sprintf("failed to verify certificate chain: \n\t%s\n", err)
return errors.New(oe)
}
certAndKey, err := fs.ReadFile(sslCert.PemFileName)
if err != nil {
return fmt.Errorf("could not read file %v for writing additional CA chains: %v", sslCert.PemFileName, err)
}
var buffer bytes.Buffer
f, err := fs.Create(sslCert.PemFileName)
if err != nil {
return fmt.Errorf("could not create PEM file %v: %v", sslCert.PemFileName, err)
}
defer f.Close()
_, err = f.Write(certAndKey)
if err != nil {
return fmt.Errorf("could not write cert and key bundle to cert file %v: %v", sslCert.PemFileName, err)
}
_, err = f.Write([]byte("\n"))
_, err = buffer.Write([]byte(sslCert.PemCertKey))
if err != nil {
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
}
_, err = f.Write(ca)
_, err = buffer.Write([]byte("\n"))
if err != nil {
return fmt.Errorf("could not append newline to cert file %v: %v", sslCert.PemFileName, err)
}
_, err = buffer.Write(ca)
if err != nil {
return fmt.Errorf("could not write ca data to cert file %v: %v", sslCert.PemFileName, err)
}
sslCert.CAFileName = sslCert.PemFileName
// since we updated sslCert.PemFileName we need to recalculate the checksum
sslCert.PemSHA = file.SHA1(sslCert.PemFileName)
return nil
return ioutil.WriteFile(sslCert.CAFileName, buffer.Bytes(), 0644)
}
// ConfigureCACert is similar to ConfigureCACertWithCertAndKey but it creates a separate file
// for CA cert and writes only ca into it and then sets relevant fields in sslCert
func ConfigureCACert(fs file.Filesystem, name string, ca []byte, sslCert *ingress.SSLCert) error {
func ConfigureCACert(name string, ca []byte, sslCert *ingress.SSLCert) error {
caName := fmt.Sprintf("ca-%v.pem", name)
fileName := fmt.Sprintf("%v/%v", file.DefaultSSLDirectory, caName)
f, err := fs.Create(fileName)
if err != nil {
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
}
defer f.Close()
_, err = f.Write(ca)
err := ioutil.WriteFile(fileName, ca, 0644)
if err != nil {
return fmt.Errorf("could not write CA file %v: %v", fileName, err)
}
sslCert.PemFileName = fileName
sslCert.CAFileName = fileName
sslCert.PemSHA = file.SHA1(fileName)
klog.V(3).Infof("Created CA Certificate for Authentication: %v", fileName)
@ -319,10 +307,10 @@ func parseSANExtension(value []byte) (dnsNames, emailAddresses []string, ipAddre
}
// AddOrUpdateDHParam creates a dh parameters file with the specified name
func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, error) {
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
pemFileName, pemName := getPemFileName(name)
tempPemFile, err := fs.TempFile(file.DefaultSSLDirectory, pemName)
tempPemFile, err := ioutil.TempFile(file.DefaultSSLDirectory, pemName)
klog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
if err != nil {
@ -339,9 +327,9 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
}
defer fs.RemoveAll(tempPemFile.Name())
defer os.Remove(tempPemFile.Name())
pemCerts, err := fs.ReadFile(tempPemFile.Name())
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
if err != nil {
return "", err
}
@ -356,7 +344,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
return "", fmt.Errorf("certificate %v contains invalid data", name)
}
err = fs.Rename(tempPemFile.Name(), pemFileName)
err = os.Rename(tempPemFile.Name(), pemFileName)
if err != nil {
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
}
@ -366,7 +354,7 @@ func AddOrUpdateDHParam(name string, dh []byte, fs file.Filesystem) (string, err
// GetFakeSSLCert creates a Self Signed Certificate
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
func GetFakeSSLCert() *ingress.SSLCert {
cert, key := getFakeHostSSLCert("ingress.local")
sslCert, err := CreateSSLCert(cert, key)
@ -374,11 +362,14 @@ func GetFakeSSLCert(fs file.Filesystem) *ingress.SSLCert {
klog.Fatalf("unexpected error creating fake SSL Cert: %v", err)
}
err = StoreSSLCertOnDisk(fs, fakeCertificateName, sslCert)
path, err := StoreSSLCertOnDisk(fakeCertificateName, sslCert)
if err != nil {
klog.Fatalf("unexpected error storing fake SSL Cert: %v", err)
}
sslCert.PemFileName = path
sslCert.PemSHA = file.SHA1(path)
return sslCert
}
@ -478,7 +469,6 @@ func IsValidHostname(hostname string, commonNames []string) bool {
type TLSListener struct {
certificatePath string
keyPath string
fs file.Filesystem
certificate *tls.Certificate
err error
lock sync.Mutex
@ -487,14 +477,9 @@ type TLSListener struct {
// NewTLSListener watches changes to th certificate and key paths
// and reloads it whenever it changes
func NewTLSListener(certificate, key string) *TLSListener {
fs, err := file.NewLocalFS()
if err != nil {
panic(fmt.Sprintf("failed to instanciate certificate: %v", err))
}
l := TLSListener{
certificatePath: certificate,
keyPath: key,
fs: fs,
lock: sync.Mutex{},
}
l.load()
@ -519,12 +504,12 @@ func (tl *TLSListener) TLSConfig() *tls.Config {
func (tl *TLSListener) load() {
klog.Infof("loading tls certificate from certificate path %s and key path %s", tl.certificatePath, tl.keyPath)
certBytes, err := tl.fs.ReadFile(tl.certificatePath)
certBytes, err := ioutil.ReadFile(tl.certificatePath)
if err != nil {
tl.certificate = nil
tl.err = err
}
keyBytes, err := tl.fs.ReadFile(tl.keyPath)
keyBytes, err := ioutil.ReadFile(tl.keyPath)
if err != nil {
tl.certificate = nil
tl.err = err

View file

@ -28,6 +28,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math"
"math/big"
"net/http"
@ -38,9 +39,6 @@ import (
"time"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/internal/file"
)
// generateRSACerts generates a self signed certificate using a self generated ca
@ -71,8 +69,6 @@ func generateRSACerts(host string) (*keyPair, *keyPair, error) {
}
func TestStoreSSLCertOnDisk(t *testing.T) {
fs := newFS(t)
cert, _, err := generateRSACerts("echoheaders")
if err != nil {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
@ -88,13 +84,13 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
}
err = StoreSSLCertOnDisk(fs, name, sslCert)
_, err = StoreSSLCertOnDisk(name, sslCert)
if err != nil {
t.Fatalf("unexpected error storing SSL certificate: %v", err)
}
if sslCert.PemFileName == "" {
t.Fatalf("expected path to pem file but returned empty")
if sslCert.PemCertKey == "" {
t.Fatalf("expected a pem certificate returned empty")
}
if len(sslCert.CN) == 0 {
@ -107,8 +103,6 @@ func TestStoreSSLCertOnDisk(t *testing.T) {
}
func TestCACert(t *testing.T) {
fs := newFS(t)
cert, CA, err := generateRSACerts("echoheaders")
if err != nil {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
@ -125,16 +119,14 @@ func TestCACert(t *testing.T) {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
}
err = StoreSSLCertOnDisk(fs, name, sslCert)
path, err := StoreSSLCertOnDisk(name, sslCert)
if err != nil {
t.Fatalf("unexpected error storing SSL certificate: %v", err)
}
if sslCert.CAFileName != "" {
t.Fatalf("expected CA file name to be empty")
}
sslCert.CAFileName = path
err = ConfigureCACertWithCertAndKey(fs, name, ca, sslCert)
err = ConfigureCACertWithCertAndKey(name, ca, sslCert)
if err != nil {
t.Fatalf("unexpected error configuring CA certificate: %v", err)
}
@ -145,9 +137,7 @@ func TestCACert(t *testing.T) {
}
func TestGetFakeSSLCert(t *testing.T) {
fs := newFS(t)
sslCert := GetFakeSSLCert(fs)
sslCert := GetFakeSSLCert()
if len(sslCert.PemCertKey) == 0 {
t.Fatalf("expected PemCertKey to not be empty")
@ -171,8 +161,6 @@ func TestGetFakeSSLCert(t *testing.T) {
}
func TestConfigureCACert(t *testing.T) {
fs := newFS(t)
cn := "demo-ca"
_, ca, err := generateRSACerts(cn)
if err != nil {
@ -191,7 +179,7 @@ func TestConfigureCACert(t *testing.T) {
t.Fatalf("expected Certificate to be set")
}
err = ConfigureCACert(fs, cn, c, sslCert)
err = ConfigureCACert(cn, c, sslCert)
if err != nil {
t.Fatalf("unexpected error creating SSL certificate: %v", err)
}
@ -200,14 +188,6 @@ func TestConfigureCACert(t *testing.T) {
}
}
func newFS(t *testing.T) file.Filesystem {
fs, err := file.NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem: %v", err)
}
return fs
}
func TestCreateSSLCert(t *testing.T) {
cert, _, err := generateRSACerts("echoheaders")
if err != nil {
@ -360,19 +340,26 @@ func encodeCertPEM(cert *x509.Certificate) []byte {
return pem.EncodeToMemory(&block)
}
func fakeCertificate(t *testing.T, fs filesystem.Filesystem) []byte {
func newFakeCertificate(t *testing.T) ([]byte, string, string) {
cert, key := getFakeHostSSLCert("localhost")
fd, err := fs.Create("/key.crt")
certFile, err := ioutil.TempFile("", "crt-")
if err != nil {
t.Errorf("failed to write test key: %v", err)
}
fd.Write(cert)
fd, err = fs.Create("/key.key")
certFile.Write(cert)
defer certFile.Close()
keyFile, err := ioutil.TempFile("", "key-")
if err != nil {
t.Errorf("failed to write test key: %v", err)
}
fd.Write(key)
return cert
keyFile.Write(key)
defer keyFile.Close()
return cert, certFile.Name(), keyFile.Name()
}
func dialTestServer(port string, rootCertificates ...[]byte) error {
@ -386,6 +373,7 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
resp, err := tls.Dial("tcp", "localhost:"+port, &tls.Config{
RootCAs: roots,
})
if err != nil {
return err
}
@ -396,13 +384,11 @@ func dialTestServer(port string, rootCertificates ...[]byte) error {
}
func TestTLSKeyReloader(t *testing.T) {
fs := filesystem.NewFakeFs()
cert := fakeCertificate(t, fs)
cert, certFile, keyFile := newFakeCertificate(t)
watcher := TLSListener{
certificatePath: "/key.crt",
keyPath: "/key.key",
fs: fs,
certificatePath: certFile,
keyPath: keyFile,
lock: sync.Mutex{},
}
watcher.load()
@ -427,19 +413,22 @@ func TestTLSKeyReloader(t *testing.T) {
})
t.Run("with a new certificate", func(t *testing.T) {
newCert := fakeCertificate(t, fs)
cert, certFile, keyFile = newFakeCertificate(t)
t.Run("when the certificate is not reloaded", func(t *testing.T) {
if dialTestServer(port, newCert) == nil {
if dialTestServer(port, cert) == nil {
t.Errorf("TLS dial should fail")
}
})
// simulate watch.NewFileWatcher to call the load function
watcher.load()
t.Run("when the certificate is reloaded", func(t *testing.T) {
if err := dialTestServer(port, newCert); err != nil {
t.Errorf("TLS dial should succeed, got error: %v", err)
}
})
})
//TODO: fix
/*
// simulate watch.NewFileWatcher to call the load function
watcher.load()
t.Run("when the certificate is reloaded", func(t *testing.T) {
if err := dialTestServer(port, cert); err != nil {
t.Errorf("TLS dial should succeed, got error: %v", err)
}
})
*/
})
}

View file

@ -23,12 +23,17 @@ import (
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/tv42/httpunix"
"k8s.io/klog"
)
// TemplatePath path of the NGINX template
var TemplatePath = "/etc/nginx/template/nginx.tmpl"
// PID defines the location of the pid file used by NGINX
var PID = "/tmp/nginx.pid"
@ -50,11 +55,17 @@ var StreamSocket = "/tmp/ingress-stream.sock"
var statusLocation = "nginx-status"
var httpClient *http.Client
func init() {
httpClient = buildUnixSocketClient(HealthCheckTimeout)
}
// NewGetStatusRequest creates a new GET request to the internal NGINX status server
func NewGetStatusRequest(path string) (int, []byte, error) {
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
res, err := buildUnixSocketClient(HealthCheckTimeout).Get(url)
res, err := httpClient.Get(url)
if err != nil {
return 0, nil, err
}
@ -70,14 +81,14 @@ func NewGetStatusRequest(path string) (int, []byte, error) {
// NewPostStatusRequest creates a new POST request to the internal NGINX status server
func NewPostStatusRequest(path, contentType string, data interface{}) (int, []byte, error) {
url := fmt.Sprintf("http+unix://%v%v", statusLocation, path)
url := fmt.Sprintf("%v://%v%v", httpunix.Scheme, statusLocation, path)
buf, err := json.Marshal(data)
if err != nil {
return 0, nil, err
}
res, err := buildUnixSocketClient(HealthCheckTimeout).Post(url, contentType, bytes.NewReader(buf))
res, err := httpClient.Post(url, contentType, bytes.NewReader(buf))
if err != nil {
return 0, nil, err
}
@ -112,11 +123,11 @@ func GetServerBlock(conf string, host string) (string, error) {
// ReadNginxConf reads the nginx configuration file into a string
func ReadNginxConf() (string, error) {
return ReadFileToString("/etc/nginx/nginx.conf")
return readFileToString("/etc/nginx/nginx.conf")
}
// ReadFileToString reads any file into a string
func ReadFileToString(path string) (string, error) {
// readFileToString reads any file into a string
func readFileToString(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
@ -142,3 +153,21 @@ func buildUnixSocketClient(timeout time.Duration) *http.Client {
Transport: u,
}
}
// Version return details about NGINX
func Version() string {
flag := "-v"
if klog.V(2) {
flag = "-V"
}
cmd := exec.Command("nginx", flag)
out, err := cmd.CombinedOutput()
if err != nil {
klog.Errorf("unexpected error obtaining NGINX version: %v", err)
return "N/A"
}
return string(out)
}

View file

@ -54,6 +54,7 @@ nav:
- Custom errors: "user-guide/custom-errors.md"
- Default backend: "user-guide/default-backend.md"
- Exposing TCP and UDP services: "user-guide/exposing-tcp-udp-services.md"
- Exposing FCGI services: "user-guide/fcgi-services.md"
- Regular expressions in paths: user-guide/ingress-path-matching.md
- External Articles: "user-guide/external-articles.md"
- Miscellaneous: "user-guide/miscellaneous.md"
@ -86,3 +87,4 @@ nav:
- Rewrite: "examples/rewrite/README.md"
- Static IPs: "examples/static-ip/README.md"
- TLS termination: "examples/tls-termination/README.md"
- Pod Security Policy (PSP): "examples/psp/README.md"

View file

@ -1,4 +1,4 @@
mkdocs-material~=4.0.2
mkdocs-material~=4.4.0
mkdocs~=1.0.4
pymdown-extensions~=6.0
pygments~=2.3.1

View file

@ -24,7 +24,8 @@ RUN clean-install \
COPY --chown=www-data:www-data . /
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types \
&& cp /usr/local/openresty/nginx/conf/fastcgi_params /etc/nginx/fastcgi_params
RUN ln -s /usr/local/openresty/nginx/modules /etc/nginx/modules
# Add LuaRocks paths

View file

@ -1,7 +1,7 @@
local ngx_balancer = require("ngx.balancer")
local cjson = require("cjson.safe")
local util = require("util")
local dns_util = require("util.dns")
local dns_lookup = require("util.dns").lookup
local configuration = require("configuration")
local round_robin = require("balancer.round_robin")
local chash = require("balancer.chash")
@ -52,7 +52,7 @@ local function resolve_external_names(original_backend)
local backend = util.deepcopy(original_backend)
local endpoints = {}
for _, endpoint in ipairs(backend.endpoints) do
local ips = dns_util.resolve(endpoint.address)
local ips = dns_lookup(endpoint.address)
for _, ip in ipairs(ips) do
table.insert(endpoints, { address = ip, port = endpoint.port })
end
@ -190,6 +190,10 @@ local function route_to_alternative_balancer(balancer)
end
local function get_balancer()
if ngx.ctx.balancer then
return ngx.ctx.balancer
end
local backend_name = ngx.var.proxy_upstream_name
local balancer = balancers[backend_name]
@ -201,9 +205,11 @@ local function get_balancer()
local alternative_backend_name = balancer.alternative_backends[1]
ngx.var.proxy_alternative_upstream_name = alternative_backend_name
return balancers[alternative_backend_name]
balancer = balancers[alternative_backend_name]
end
ngx.ctx.balancer = balancer
return balancer
end
@ -260,6 +266,7 @@ if _TEST then
_M.get_implementation = get_implementation
_M.sync_backend = sync_backend
_M.route_to_alternative_balancer = route_to_alternative_balancer
_M.get_balancer = get_balancer
end
return _M

View file

@ -5,6 +5,7 @@
-- /finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala
local resty_lock = require("resty.lock")
local util = require("util")
local split = require("util.split")
@ -13,10 +14,36 @@ local ngx_log = ngx.log
local INFO = ngx.INFO
local DECAY_TIME = 10 -- this value is in seconds
local LOCK_KEY = ":ewma_key"
local PICK_SET_SIZE = 2
local ewma_lock, ewma_lock_err = resty_lock:new("balancer_ewma_locks", {timeout = 0, exptime = 0.1})
if not ewma_lock then
error(ewma_lock_err)
end
local _M = { name = "ewma" }
local function lock(upstream)
local _, err = ewma_lock:lock(upstream .. LOCK_KEY)
if err then
if err ~= "timeout" then
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to lock: %s", tostring(err)))
end
end
return err
end
local function unlock()
local ok, err = ewma_lock:unlock()
if not ok then
ngx.log(ngx.ERR, string.format("EWMA Balancer failed to unlock: %s", tostring(err)))
end
return err
end
local function decay_ewma(ewma, last_touched_at, rtt, now)
local td = now - last_touched_at
td = (td > 0) and td or 0
@ -26,28 +53,55 @@ local function decay_ewma(ewma, last_touched_at, rtt, now)
return ewma
end
local function get_or_update_ewma(self, upstream, rtt, update)
local ewma = self.ewma[upstream] or 0
local function store_stats(upstream, ewma, now)
local success, err, forcible = ngx.shared.balancer_ewma_last_touched_at:set(upstream, now)
if not success then
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set failed " .. err)
end
if forcible then
ngx.log(ngx.WARN, "balancer_ewma_last_touched_at:set valid items forcibly overwritten")
end
success, err, forcible = ngx.shared.balancer_ewma:set(upstream, ewma)
if not success then
ngx.log(ngx.WARN, "balancer_ewma:set failed " .. err)
end
if forcible then
ngx.log(ngx.WARN, "balancer_ewma:set valid items forcibly overwritten")
end
end
local function get_or_update_ewma(upstream, rtt, update)
local lock_err = nil
if update then
lock_err = lock(upstream)
end
local ewma = ngx.shared.balancer_ewma:get(upstream) or 0
if lock_err ~= nil then
return ewma, lock_err
end
local now = ngx.now()
local last_touched_at = self.ewma_last_touched_at[upstream] or 0
local last_touched_at = ngx.shared.balancer_ewma_last_touched_at:get(upstream) or 0
ewma = decay_ewma(ewma, last_touched_at, rtt, now)
if not update then
return ewma, nil
end
self.ewma[upstream] = ewma
self.ewma_last_touched_at[upstream] = now
store_stats(upstream, ewma, now)
unlock()
return ewma, nil
end
local function score(self, upstream)
local function score(upstream)
-- Original implementation used names
-- Endpoints don't have names, so passing in IP:Port as key instead
local upstream_name = upstream.address .. ":" .. upstream.port
return get_or_update_ewma(self, upstream_name, 0, false)
return get_or_update_ewma(upstream_name, 0, false)
end
-- implementation similar to https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
@ -63,12 +117,12 @@ local function shuffle_peers(peers, k)
-- peers[1 .. k] will now contain a randomly selected k from #peers
end
local function pick_and_score(self, peers, k)
local function pick_and_score(peers, k)
shuffle_peers(peers, k)
local lowest_score_index = 1
local lowest_score = score(self, peers[lowest_score_index])
local lowest_score = score(peers[lowest_score_index])
for i = 2, k do
local new_score = score(self, peers[i])
local new_score = score(peers[i])
if new_score < lowest_score then
lowest_score_index, lowest_score = i, new_score
end
@ -76,6 +130,31 @@ local function pick_and_score(self, peers, k)
return peers[lowest_score_index], lowest_score
end
-- slow_start_ewma is something we use to avoid sending too many requests
-- to the newly introduced endpoints. We currently use average ewma values
-- of existing endpoints.
local function calculate_slow_start_ewma(self)
local total_ewma = 0
local endpoints_count = 0
for _, endpoint in pairs(self.peers) do
local endpoint_string = endpoint.address .. ":" .. endpoint.port
local ewma = ngx.shared.balancer_ewma:get(endpoint_string)
if ewma then
endpoints_count = endpoints_count + 1
total_ewma = total_ewma + ewma
end
end
if endpoints_count == 0 then
ngx.log(ngx.INFO, "no ewma value exists for the endpoints")
return nil
end
return total_ewma / endpoints_count
end
function _M.balance(self)
local peers = self.peers
local endpoint, ewma_score = peers[1], -1
@ -83,7 +162,7 @@ function _M.balance(self)
if #peers > 1 then
local k = (#peers < PICK_SET_SIZE) and #peers or PICK_SET_SIZE
local peer_copy = util.deepcopy(peers)
endpoint, ewma_score = pick_and_score(self, peer_copy, k)
endpoint, ewma_score = pick_and_score(peer_copy, k)
end
ngx.var.balancer_ewma_score = ewma_score
@ -92,7 +171,7 @@ function _M.balance(self)
return endpoint.address .. ":" .. endpoint.port
end
function _M.after_balance(self)
function _M.after_balance(_)
local response_time = tonumber(split.get_first_value(ngx.var.upstream_response_time)) or 0
local connect_time = tonumber(split.get_first_value(ngx.var.upstream_connect_time)) or 0
local rtt = connect_time + response_time
@ -101,30 +180,41 @@ function _M.after_balance(self)
if util.is_blank(upstream) then
return
end
get_or_update_ewma(self, upstream, rtt, true)
get_or_update_ewma(upstream, rtt, true)
end
function _M.sync(self, backend)
self.traffic_shaping_policy = backend.trafficShapingPolicy
self.alternative_backends = backend.alternativeBackends
local normalized_endpoints_added, normalized_endpoints_removed = util.diff_endpoints(self.peers, backend.endpoints)
local changed = not util.deep_compare(self.peers, backend.endpoints)
if not changed then
if #normalized_endpoints_added == 0 and #normalized_endpoints_removed == 0 then
ngx.log(ngx.INFO, "endpoints did not change for backend " .. tostring(backend.name))
return
end
ngx_log(INFO, string_format("[%s] peers have changed for backend %s", self.name, backend.name))
self.traffic_shaping_policy = backend.trafficShapingPolicy
self.alternative_backends = backend.alternativeBackends
self.peers = backend.endpoints
self.ewma = {}
self.ewma_last_touched_at = {}
for _, endpoint_string in ipairs(normalized_endpoints_removed) do
ngx.shared.balancer_ewma:delete(endpoint_string)
ngx.shared.balancer_ewma_last_touched_at:delete(endpoint_string)
end
local slow_start_ewma = calculate_slow_start_ewma(self)
if slow_start_ewma ~= nil then
local now = ngx.now()
for _, endpoint_string in ipairs(normalized_endpoints_added) do
store_stats(endpoint_string, slow_start_ewma, now)
end
end
end
function _M.new(self, backend)
local o = {
peers = backend.endpoints,
ewma = {},
ewma_last_touched_at = {},
traffic_shaping_policy = backend.trafficShapingPolicy,
alternative_backends = backend.alternativeBackends,
}

View file

@ -4,9 +4,7 @@ local cjson = require("cjson.safe")
local configuration_data = ngx.shared.configuration_data
local certificate_data = ngx.shared.certificate_data
local _M = {
nameservers = {}
}
local _M = {}
function _M.get_backends_data()
return configuration_data:get("backends")

View file

@ -1,7 +1,7 @@
local ngx_balancer = require("ngx.balancer")
local cjson = require("cjson.safe")
local util = require("util")
local dns_util = require("util.dns")
local dns_lookup = require("util.dns").lookup
local configuration = require("tcp_udp_configuration")
local round_robin = require("balancer.round_robin")
@ -34,7 +34,7 @@ local function resolve_external_names(original_backend)
local backend = util.deepcopy(original_backend)
local endpoints = {}
for _, endpoint in ipairs(backend.endpoints) do
local ips = dns_util.resolve(endpoint.address)
local ips = dns_lookup(endpoint.address)
for _, ip in ipairs(ips) do
table.insert(endpoints, {address = ip, port = endpoint.port})
end

View file

@ -1,91 +1,151 @@
local util = require("util")
local original_ngx = ngx
local function reset_ngx()
_G.ngx = original_ngx
end
local function mock_ngx(mock)
local _ngx = mock
setmetatable(_ngx, { __index = ngx })
_G.ngx = _ngx
end
local function flush_all_ewma_stats()
ngx.shared.balancer_ewma:flush_all()
ngx.shared.balancer_ewma_last_touched_at:flush_all()
end
local function store_ewma_stats(endpoint_string, ewma, touched_at)
ngx.shared.balancer_ewma:set(endpoint_string, ewma)
ngx.shared.balancer_ewma_last_touched_at:set(endpoint_string, touched_at)
end
local function assert_ewma_stats(endpoint_string, ewma, touched_at)
assert.are.equals(ewma, ngx.shared.balancer_ewma:get(endpoint_string))
assert.are.equals(touched_at, ngx.shared.balancer_ewma_last_touched_at:get(endpoint_string))
end
describe("Balancer ewma", function()
local balancer_ewma = require("balancer.ewma")
local ngx_now = 1543238266
local backend, instance
before_each(function()
mock_ngx({ now = function() return ngx_now end, var = { balancer_ewma_score = -1 } })
backend = {
name = "namespace-service-port", ["load-balance"] = "ewma",
endpoints = {
{ address = "10.10.10.1", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.10.10.2", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.10.10.3", port = "8080", maxFails = 0, failTimeout = 0 },
}
}
store_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
store_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
store_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
instance = balancer_ewma:new(backend)
end)
after_each(function()
reset_ngx()
flush_all_ewma_stats()
end)
describe("after_balance()", function()
local ngx_now = 1543238266
_G.ngx.now = function() return ngx_now end
_G.ngx.var = { upstream_response_time = "0.25", upstream_connect_time = "0.02", upstream_addr = "10.184.7.40:8080" }
it("updates EWMA stats", function()
local backend = {
name = "my-dummy-backend", ["load-balance"] = "ewma",
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
}
local instance = balancer_ewma:new(backend)
ngx.var = { upstream_addr = "10.10.10.2:8080", upstream_connect_time = "0.02", upstream_response_time = "0.1" }
instance:after_balance()
assert.equal(0.27, instance.ewma[ngx.var.upstream_addr])
assert.equal(ngx_now, instance.ewma_last_touched_at[ngx.var.upstream_addr])
local weight = math.exp(-5 / 10)
local expected_ewma = 0.3 * weight + 0.12 * (1.0 - weight)
assert.are.equals(expected_ewma, ngx.shared.balancer_ewma:get(ngx.var.upstream_addr))
assert.are.equals(ngx_now, ngx.shared.balancer_ewma_last_touched_at:get(ngx.var.upstream_addr))
end)
end)
describe("balance()", function()
it("returns single endpoint when the given backend has only one endpoint", function()
local backend = {
name = "my-dummy-backend", ["load-balance"] = "ewma",
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
}
local instance = balancer_ewma:new(backend)
local single_endpoint_backend = util.deepcopy(backend)
table.remove(single_endpoint_backend.endpoints, 3)
table.remove(single_endpoint_backend.endpoints, 2)
local single_endpoint_instance = balancer_ewma:new(single_endpoint_backend)
local peer = instance:balance()
assert.equal("10.184.7.40:8080", peer)
local peer = single_endpoint_instance:balance()
assert.are.equals("10.10.10.1:8080", peer)
assert.are.equals(-1, ngx.var.balancer_ewma_score)
end)
it("picks the endpoint with lowest score when there two of them", function()
local backend = {
name = "my-dummy-backend", ["load-balance"] = "ewma",
endpoints = {
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
}
}
local instance = balancer_ewma:new(backend)
instance.ewma = { ["10.184.7.40:8080"] = 0.5, ["10.184.97.100:8080"] = 0.3 }
instance.ewma_last_touched_at = { ["10.184.7.40:8080"] = ngx.now(), ["10.184.97.100:8080"] = ngx.now() }
it("picks the endpoint with lowest decayed score", function()
local two_endpoints_backend = util.deepcopy(backend)
table.remove(two_endpoints_backend.endpoints, 2)
local two_endpoints_instance = balancer_ewma:new(two_endpoints_backend)
local peer = instance:balance()
assert.equal("10.184.97.100:8080", peer)
local peer = two_endpoints_instance:balance()
-- even though 10.10.10.1:8080 has a lower ewma score
-- algorithm picks 10.10.10.3:8080 because its decayed score is even lower
assert.equal("10.10.10.3:8080", peer)
assert.are.equals(0.16240233988393523723, ngx.var.balancer_ewma_score)
end)
end)
describe("sync()", function()
local backend, instance
before_each(function()
backend = {
name = "my-dummy-backend", ["load-balance"] = "ewma",
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
}
instance = balancer_ewma:new(backend)
end)
it("does nothing when endpoints do not change", function()
local new_backend = {
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } }
}
instance:sync(new_backend)
end)
it("updates endpoints", function()
local new_backend = {
endpoints = {
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
{ address = "10.184.97.100", port = "8080", maxFails = 0, failTimeout = 0 },
}
}
instance:sync(new_backend)
assert.are.same(new_backend.endpoints, instance.peers)
end)
it("resets stats", function()
it("does not reset stats when endpoints do not change", function()
local new_backend = util.deepcopy(backend)
new_backend.endpoints[1].maxFails = 3
instance:sync(new_backend)
assert.are.same(new_backend.endpoints, instance.peers)
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
assert_ewma_stats("10.10.10.2:8080", 0.3, ngx_now - 5)
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
end)
it("updates peers, deletes stats for old endpoints and sets average ewma score to new ones", function()
local new_backend = util.deepcopy(backend)
-- existing endpoint 10.10.10.2 got deleted
-- and replaced with 10.10.10.4
new_backend.endpoints[2].address = "10.10.10.4"
-- and there's one new extra endpoint
table.insert(new_backend.endpoints, { address = "10.10.10.5", port = "8080", maxFails = 0, failTimeout = 0 })
instance:sync(new_backend)
assert.are.same(new_backend.endpoints, instance.peers)
assert_ewma_stats("10.10.10.1:8080", 0.2, ngx_now - 1)
assert_ewma_stats("10.10.10.2:8080", nil, nil)
assert_ewma_stats("10.10.10.3:8080", 1.2, ngx_now - 20)
local slow_start_ewma = (0.2 + 1.2) / 2
assert_ewma_stats("10.10.10.4:8080", slow_start_ewma, ngx_now)
assert_ewma_stats("10.10.10.5:8080", slow_start_ewma, ngx_now)
end)
it("does not set slow_start_ewma when there is no existing ewma", function()
local new_backend = util.deepcopy(backend)
table.insert(new_backend.endpoints, { address = "10.10.10.4", port = "8080", maxFails = 0, failTimeout = 0 })
-- when the LB algorithm instance is just instantiated it won't have any
-- ewma value set for the initial endpoints (because it has not processed any request yet),
-- this test is trying to simulate that by flushing existing ewma values
flush_all_ewma_stats()
instance:sync(new_backend)
assert_ewma_stats("10.10.10.1:8080", nil, nil)
assert_ewma_stats("10.10.10.2:8080", nil, nil)
assert_ewma_stats("10.10.10.3:8080", nil, nil)
assert_ewma_stats("10.10.10.4:8080", nil, nil)
end)
end)
end)

View file

@ -33,7 +33,7 @@ local function reset_backends()
backends = {
{
name = "access-router-production-web-80", port = "80", secure = false,
secureCACert = { secret = "", caFilename = "", pemSha = "" },
secureCACert = { secret = "", caFilename = "", caSha = "" },
sslPassthrough = false,
endpoints = {
{ address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 },
@ -49,7 +49,7 @@ local function reset_backends()
},
},
{ name = "my-dummy-app-1", ["load-balance"] = "round_robin", },
{
{
name = "my-dummy-app-2", ["load-balance"] = "chash",
upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", },
},
@ -86,6 +86,44 @@ describe("Balancer", function()
end)
end)
describe("get_balancer()", function()
it("always returns the same balancer for given request context", function()
local backend = {
name = "my-dummy-app-6", ["load-balance"] = "ewma",
alternativeBackends = { "my-dummy-canary-app-6" },
endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
trafficShapingPolicy = {
weight = 0,
header = "",
headerValue = "",
cookie = ""
},
}
local canary_backend = {
name = "my-dummy-canary-app-6", ["load-balance"] = "ewma",
alternativeBackends = { "my-dummy-canary-app-6" },
endpoints = { { address = "11.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
trafficShapingPolicy = {
weight = 5,
header = "",
headerValue = "",
cookie = ""
},
}
balancer.sync_backend(backend)
balancer.sync_backend(canary_backend)
mock_ngx({ var = { proxy_upstream_name = backend.name } })
local expected = balancer.get_balancer()
for i = 1,50,1 do
assert.are.same(expected, balancer.get_balancer())
end
end)
end)
describe("route_to_alternative_balancer()", function()
local backend, _balancer
@ -277,8 +315,7 @@ describe("Balancer", function()
}
}
local dns_helper = require("test/dns_helper")
dns_helper.mock_dns_query({
helpers.mock_resty_dns_query(nil, {
{
name = "example.com",
address = "192.168.1.1",

View file

@ -115,15 +115,20 @@ describe("Configuration", function()
end)
it("returns a status of 400", function()
local original_io_open = _G.io.open
_G.io.open = function(filename, extension) return false end
assert.has_no.errors(configuration.call)
assert.equal(ngx.status, ngx.HTTP_BAD_REQUEST)
_G.io.open = original_io_open
end)
it("logs 'dynamic-configuration: unable to read valid request body to stderr'", function()
local original_io_open = _G.io.open
_G.io.open = function(filename, extension) return false end
local s = spy.on(ngx, "log")
assert.has_no.errors(configuration.call)
assert.spy(s).was_called_with(ngx.ERR, "dynamic-configuration: unable to read valid request body")
_G.io.open = original_io_open
end)
end)

View file

@ -1,27 +0,0 @@
local _M = {}
local configuration = require("configuration")
local resolver = require("resty.dns.resolver")
local old_resolver_new = resolver.new
local function reset(nameservers)
configuration.nameservers = nameservers or { "1.1.1.1" }
end
function _M.mock_new(func, nameservers)
reset(nameservers)
resolver.new = func
end
function _M.mock_dns_query(response, err)
reset()
resolver.new = function(self, options)
local r = old_resolver_new(self, options)
r.query = function(self, name, options, tries)
return response, err
end
return r
end
end
return _M

View file

@ -0,0 +1,50 @@
local _M = {}
local resty_dns_resolver = require("resty.dns.resolver")
local original_resty_dns_resolver_new = resty_dns_resolver.new
local original_io_open = io.open
function _M.with_resolv_conf(content, func)
local new_resolv_conf_f = assert(io.tmpfile())
new_resolv_conf_f:write(content)
new_resolv_conf_f:seek("set", 0)
io.open = function(path, mode)
if path ~= "/etc/resolv.conf" then
error("expected '/etc/resolv.conf' as path but got: " .. tostring(path))
end
if mode ~= "r" then
error("expected 'r' as mode but got: " .. tostring(mode))
end
return new_resolv_conf_f, nil
end
func()
io.open = original_io_open
if io.type(new_resolv_conf_f) ~= "closed file" then
error("file was left open")
end
end
function _M.mock_resty_dns_new(func)
resty_dns_resolver.new = func
end
function _M.mock_resty_dns_query(mocked_host, response, err)
resty_dns_resolver.new = function(self, options)
local r = original_resty_dns_resolver_new(self, options)
r.query = function(self, host, options, tries)
if mocked_host and mocked_host ~= host then
return error(tostring(host) .. " is not mocked")
end
return response, err
end
return r
end
end
return _M

View file

@ -11,6 +11,7 @@ do
-- if there's more constants need to be whitelisted for test runs, add here.
local GLOBALS_ALLOWED_IN_TEST = {
_TEST = true,
helpers = true,
}
local newindex = function(table, key, value)
rawset(table, key, value)
@ -33,6 +34,8 @@ do
setmetatable(_G, { __newindex = newindex })
end
_G.helpers = require("test.helpers")
_G._TEST = true
local ffi = require("ffi")
local lua_ingress = require("lua_ingress")

View file

@ -1,43 +1,89 @@
describe("resolve", function()
local dns = require("util.dns")
local dns_helper = require("test/dns_helper")
local conf = [===[
nameserver 1.2.3.4
nameserver 4.5.6.7
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
]===]
helpers.with_resolv_conf(conf, function()
require("util.resolv_conf")
end)
describe("dns.lookup", function()
local dns, dns_lookup, spy_ngx_log
before_each(function()
spy_ngx_log = spy.on(ngx, "log")
dns = require("util.dns")
dns_lookup = dns.lookup
end)
after_each(function()
package.loaded["util.dns"] = nil
end)
it("sets correct nameservers", function()
dns_helper.mock_new(function(self, options)
helpers.mock_resty_dns_new(function(self, options)
assert.are.same({ nameservers = { "1.2.3.4", "4.5.6.7" }, retrans = 5, timeout = 2000 }, options)
return nil, ""
end, { "1.2.3.4", "4.5.6.7" })
dns.resolve("example.com")
end)
dns_lookup("example.com")
end)
it("returns host when an error happens", function()
local s_ngx_log = spy.on(ngx, "log")
describe("when there's an error", function()
it("returns host when resolver can not be instantiated", function()
helpers.mock_resty_dns_new(function(...) return nil, "an error" end)
assert.are.same({ "example.com" }, dns_lookup("example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
end)
dns_helper.mock_new(function(...) return nil, "an error" end)
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to instantiate the resolver: an error")
it("returns host when the query returns nil", function()
helpers.mock_resty_dns_query(nil, nil, "oops!")
assert.are.same({ "example.com" }, dns_lookup("example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
end)
dns_helper.mock_dns_query(nil, "oops!")
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
it("returns host when the query returns empty answer", function()
helpers.mock_resty_dns_query(nil, {})
assert.are.same({ "example.com" }, dns_lookup("example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
end)
dns_helper.mock_dns_query({ errcode = 1, errstr = "format error" })
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nserver returned error code: 1: format error\nserver returned error code: 1: format error")
it("returns host when there's answer but with error", function()
helpers.mock_resty_dns_query(nil, { errcode = 1, errstr = "format error" })
assert.are.same({ "example.com" }, dns_lookup("example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\n" ..
"server returned error code: 1: format error\nserver returned error code: 1: format error")
end)
dns_helper.mock_dns_query({})
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
it("retuns host when there's answer but no A/AAAA record in it", function()
helpers.mock_resty_dns_query(nil, { { name = "example.com", cname = "sub.example.com", ttl = 60 } })
assert.are.same({ "example.com" }, dns_lookup("example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno A record resolved\nno AAAA record resolved")
end)
dns_helper.mock_dns_query({ { name = "example.com", cname = "sub.example.com", ttl = 60 } })
assert.are.same({ "example.com" }, dns.resolve("example.com"))
assert.spy(s_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\nno record resolved\nno record resolved")
it("returns host when the query returns nil and number of dots is not less than configured ndots", function()
helpers.mock_resty_dns_query(nil, nil, "oops!")
assert.are.same({ "a.b.c.d.example.com" }, dns_lookup("a.b.c.d.example.com"))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
end)
it("returns host when the query returns nil for a fully qualified domain", function()
helpers.mock_resty_dns_query("example.com.", nil, "oops!")
assert.are.same({ "example.com." }, dns_lookup("example.com."))
assert.spy(spy_ngx_log).was_called_with(ngx.ERR, "failed to query the DNS server:\noops!\noops!")
end)
end)
it("resolves all A records of given host, caches them with minimal ttl and returns from cache next time", function()
dns_helper.mock_dns_query({
it("returns answer from cache if it exists without doing actual DNS query", function()
dns._cache:set("example.com", { "192.168.1.1" })
assert.are.same({ "192.168.1.1" }, dns_lookup("example.com"))
end)
it("resolves a fully qualified domain without looking at resolv.conf search and caches result", function()
helpers.mock_resty_dns_query("example.com.", {
{
name = "example.com",
name = "example.com.",
address = "192.168.1.1",
ttl = 3600,
},
@ -47,28 +93,43 @@ describe("resolve", function()
ttl = 60,
}
})
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns._cache:get("example.com."))
end)
local lrucache = require("resty.lrucache")
local old_lrucache_new = lrucache.new
lrucache.new = function(...)
local cache = old_lrucache_new(...)
it("starts with host itself when number of dots is not less than configured ndots", function()
local host = "a.b.c.d.example.com"
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
local old_set = cache.set
cache.set = function(self, key, value, ttl)
assert.equal("example.com", key)
assert.are.same({ "192.168.1.1", "1.2.3.4" }, value)
assert.equal(60, ttl)
return old_set(self, key, value, ttl)
end
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
end)
return cache
end
it("starts with first search entry when number of dots is less than configured ndots", function()
local host = "example.com.ingress-nginx.svc.cluster.local"
helpers.mock_resty_dns_query(host, { { name = host, address = "192.168.1.1", ttl = 3600, } } )
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
assert.are.same({ "192.168.1.1" }, dns_lookup(host))
assert.are.same({ "192.168.1.1" }, dns._cache:get(host))
end)
dns_helper.mock_new(function(...)
error("expected to short-circuit and return response from cache")
end)
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns.resolve("example.com"))
it("it caches with minimal ttl", function()
helpers.mock_resty_dns_query("example.com.", {
{
name = "example.com.",
address = "192.168.1.1",
ttl = 3600,
},
{
name = "example.com.",
address = "1.2.3.4",
ttl = 60,
}
})
local spy_cache_set = spy.on(dns._cache, "set")
assert.are.same({ "192.168.1.1", "1.2.3.4" }, dns_lookup("example.com."))
assert.spy(spy_cache_set).was_called_with(match.is_table(), "example.com.", { "192.168.1.1", "1.2.3.4" }, 60)
end)
end)

View file

@ -0,0 +1,64 @@
local original_io_open = io.open
describe("resolv_conf", function()
after_each(function()
package.loaded["util.resolv_conf"] = nil
io.open = original_io_open
end)
it("errors when file can not be opened", function()
io.open = function(...)
return nil, "file does not exist"
end
assert.has_error(function() require("util.resolv_conf") end, "could not open /etc/resolv.conf: file does not exist")
end)
it("opens '/etc/resolv.conf' with mode 'r'", function()
io.open = function(path, mode)
assert.are.same("/etc/resolv.conf", path)
assert.are.same("r", mode)
return original_io_open(path, mode)
end
assert.has_no.errors(function() require("util.resolv_conf") end)
end)
it("correctly parses resolv.conf", function()
local conf = [===[
# This is a comment
nameserver 10.96.0.10
nameserver 10.96.0.99
search ingress-nginx.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
]===]
helpers.with_resolv_conf(conf, function()
local resolv_conf = require("util.resolv_conf")
assert.are.same({
nameservers = { "10.96.0.10", "10.96.0.99" },
search = { "ingress-nginx.svc.cluster.local", "svc.cluster.local", "cluster.local" },
ndots = 5,
}, resolv_conf)
end)
end)
it("ignores options that it does not understand", function()
local conf = [===[
nameserver 10.96.0.10
search example.com
options debug
options ndots:3
]===]
helpers.with_resolv_conf(conf, function()
local resolv_conf = require("util.resolv_conf")
assert.are.same({
nameservers = { "10.96.0.10" },
search = { "example.com" },
ndots = 3,
}, resolv_conf)
end)
end)
end)

View file

@ -12,24 +12,87 @@ end
describe("lua_ngx_var", function()
local util = require("util")
before_each(function()
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
end)
after_each(function()
reset_ngx()
package.loaded["monitor"] = nil
end)
it("returns value of nginx var by key", function()
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
describe("lua_ngx_var", function()
before_each(function()
mock_ngx({ var = { remote_addr = "192.168.1.1", [1] = "nginx/regexp/1/group/capturing" } })
end)
it("returns value of nginx var by key", function()
assert.equal("192.168.1.1", util.lua_ngx_var("$remote_addr"))
end)
it("returns value of nginx var when key is number", function()
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
end)
it("returns nil when variable is not defined", function()
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
end)
end)
it("returns value of nginx var when key is number", function()
assert.equal("nginx/regexp/1/group/capturing", util.lua_ngx_var("$1"))
end)
describe("diff_endpoints", function()
it("returns removed and added endpoints", function()
local old = {
{ address = "10.10.10.1", port = "8080" },
{ address = "10.10.10.2", port = "8080" },
{ address = "10.10.10.3", port = "8080" },
}
local new = {
{ address = "10.10.10.1", port = "8080" },
{ address = "10.10.10.2", port = "8081" },
{ address = "11.10.10.2", port = "8080" },
{ address = "11.10.10.3", port = "8080" },
}
local expected_added = { "10.10.10.2:8081", "11.10.10.2:8080", "11.10.10.3:8080" }
table.sort(expected_added)
local expected_removed = { "10.10.10.2:8080", "10.10.10.3:8080" }
table.sort(expected_removed)
it("returns nil when variable is not defined", function()
assert.equal(nil, util.lua_ngx_var("$foo_bar"))
local added, removed = util.diff_endpoints(old, new)
table.sort(added)
table.sort(removed)
assert.are.same(expected_added, added)
assert.are.same(expected_removed, removed)
end)
it("returns empty results for empty inputs", function()
local added, removed = util.diff_endpoints({}, {})
assert.are.same({}, added)
assert.are.same({}, removed)
end)
it("returns empty results for same inputs", function()
local old = {
{ address = "10.10.10.1", port = "8080" },
{ address = "10.10.10.2", port = "8080" },
{ address = "10.10.10.3", port = "8080" },
}
local new = util.deepcopy(old)
local added, removed = util.diff_endpoints(old, new)
assert.are.same({}, added)
assert.are.same({}, removed)
end)
it("handles endpoints with nil attribute", function()
local old = {
{ address = nil, port = "8080" },
{ address = "10.10.10.2", port = "8080" },
{ address = "10.10.10.3", port = "8080" },
}
local new = util.deepcopy(old)
new[2].port = nil
local added, removed = util.diff_endpoints(old, new)
assert.are.same({ "10.10.10.2:nil" }, added)
assert.are.same({ "10.10.10.2:8080" }, removed)
end)
end)
end)

View file

@ -1,5 +1,6 @@
local string_len = string.len
local string_sub = string.sub
local string_format = string.format
local _M = {}
@ -26,6 +27,44 @@ function _M.lua_ngx_var(ngx_var)
return ngx.var[var_name]
end
-- normalize_endpoints takes endpoints as an array of endpoint objects
-- and returns a table where keys are string that's endpoint.address .. ":" .. endpoint.port
-- and values are all true
local function normalize_endpoints(endpoints)
local normalized_endpoints = {}
for _, endpoint in pairs(endpoints) do
local endpoint_string = string_format("%s:%s", endpoint.address, endpoint.port)
normalized_endpoints[endpoint_string] = true
end
return normalized_endpoints
end
-- diff_endpoints compares old and new
-- and as a first argument returns what endpoints are in new
-- but are not in old, and as a second argument it returns
-- what endpoints are in old but are in new.
-- Both return values are normalized (ip:port).
function _M.diff_endpoints(old, new)
local endpoints_added, endpoints_removed = {}, {}
local normalized_old, normalized_new = normalize_endpoints(old), normalize_endpoints(new)
for endpoint_string, _ in pairs(normalized_old) do
if not normalized_new[endpoint_string] then
table.insert(endpoints_removed, endpoint_string)
end
end
for endpoint_string, _ in pairs(normalized_new) do
if not normalized_old[endpoint_string] then
table.insert(endpoints_added, endpoint_string)
end
end
return endpoints_added, endpoints_removed
end
-- this implementation is taken from
-- https://web.archive.org/web/20131225070434/http://snippets.luacode.org/snippets/Deep_Comparison_of_Two_Values_3
-- and modified for use in this project

Some files were not shown because too many files have changed in this diff Show more