diff --git a/components/relay/Dockerfile b/components/relay/Dockerfile new file mode 100644 index 0000000..89370d9 --- /dev/null +++ b/components/relay/Dockerfile @@ -0,0 +1,50 @@ +# Build the manager binary +FROM golang:1.16.6 as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum + +# setup github user and token +ARG BUILD_USR="nothing" +ARG BUILD_PWD="nothing" + +# Create the user and group files that will be used in the running +# container to run the process as an unprivileged user. +RUN mkdir /user && \ + echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \ + echo 'nobody:x:65534:' > /user/group + +# Create a netrc file using the credentials specified using --build-arg +RUN printf "machine github.com\n\ + login ${BUILD_USR}\n\ + password ${BUILD_PWD}\n\ + \n\ + machine api.github.com\n\ + login ${BUILD_USR}\n\ + password ${BUILD_PWD}\n"\ + >> /root/.netrc +RUN chmod 600 /root/.netrc + +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN GOPRIVATE=github.com/RafaySystems/* go mod download + +# Copy the go source +COPY main.go main.go +COPY pkg/ pkg/ + + +# Build +# uncomment this to debug race conditions +#RUN GOPRIVATE=github.com/RafaySystems/* CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -race -a -o rafay-relay main.go +RUN GOPRIVATE=github.com/RafaySystems/* CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" -a -o rafay-relay main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:latest +WORKDIR / +COPY --from=builder /workspace/relay . +ENTRYPOINT ["/relay"] + diff --git a/components/relay/Dockerfile.agent b/components/relay/Dockerfile.agent new file mode 100644 index 0000000..c50e75c --- /dev/null +++ b/components/relay/Dockerfile.agent @@ -0,0 +1,65 @@ +# Build the manager binary +FROM golang:1.16.6 as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum + +# setup github user and token +ARG BUILD_USR="nothing" +ARG BUILD_PWD="nothing" + +# Create the user and group files that will be used in the running +# container to run the process as an unprivileged user. +RUN mkdir /user && \ + echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \ + echo 'nobody:x:65534:' > /user/group + +# Create a netrc file using the credentials specified using --build-arg +RUN printf "machine github.com\n\ + login ${BUILD_USR}\n\ + password ${BUILD_PWD}\n\ + \n\ + machine api.github.com\n\ + login ${BUILD_USR}\n\ + password ${BUILD_PWD}\n"\ + >> /root/.netrc +RUN chmod 600 /root/.netrc + +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN GOPRIVATE=github.com/RafaySystems/* go mod download + +# Copy the go source +COPY main.go main.go +COPY pkg/ pkg/ + + +# Build +# uncomment this to debug race conditions +#RUN GOPRIVATE=github.com/RafaySystems/* CGO_ENABLED=1 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -race -a -o rafay-relay main.go +RUN GOPRIVATE=github.com/RafaySystems/* CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" -a -o rafay-relay main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:latest +WORKDIR / +COPY --from=builder /workspace/relay . +ENTRYPOINT ["/relay"] + + +# Before copying the Go binary directly to the final image, +# add them to the intermdediate upx image +FROM rafaysystems/upx:latest as upx +COPY --from=builder /workspace/relay /relay.orig + +# Compress the binary and copy it to final image +RUN upx --best --lzma -o /relay /relay.orig + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=upx /relay . +ENTRYPOINT ["/relay"] diff --git a/components/relay/Dockerfile.dev b/components/relay/Dockerfile.dev new file mode 100644 index 0000000..125f600 --- /dev/null +++ b/components/relay/Dockerfile.dev @@ -0,0 +1,8 @@ +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +#FROM gcr.io/distroless/static:latest +## use base:debug for shell access +FROM gcr.io/distroless/static:latest +WORKDIR / +COPY relay.upx relay +ENTRYPOINT ["/relay"] diff --git a/components/relay/Makefile b/components/relay/Makefile new file mode 100644 index 0000000..062dcb5 --- /dev/null +++ b/components/relay/Makefile @@ -0,0 +1,33 @@ +IMG ?= relay:latest +TS := $(shell /bin/date "+%Y%m%d%H%M%S") +DEV_USER ?= dev +DEV_TAG := registry.dev.rafay-edge.net:5000/${DEV_USER}/relay:$(TS) + +.PHONY: tidy +tidy: + GOPRIVATE=github.com/RafaySystems/* go mod tidy + +.PHONY: vendor +vendor: + GOPROXY=direct GOPRIVATE=github.com/RafaySystems/* go mod vendor + +check: + go fmt ./... + go vet ./... + +build: + docker build . -t ${IMG} --build-arg BUILD_USR=${BUILD_USER} --build-arg BUILD_PWD=${BUILD_PASSWORD} + +build-agent: + docker build -t ${IMG} --build-arg BUILD_USR=${BUILD_USER} --build-arg BUILD_PWD=${BUILD_PASSWORD} -f Dockerfile.agent . + +tag-dev: + docker tag ${IMG} $(DEV_TAG) + docker push $(DEV_TAG) + +build-dev: + rm -rf relay.* + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" -o relay.dev main.go + upx -5 -o relay.upx relay.dev + docker build -f Dockerfile.dev -t ${IMG} . + $(MAKE) tag-dev diff --git a/components/relay/README.md b/components/relay/README.md new file mode 100644 index 0000000..9bf89aa --- /dev/null +++ b/components/relay/README.md @@ -0,0 +1,137 @@ +# Rafay relay-server / rafay-agent +Relay server and agent for kubectl access. + +## Development Setup +Relay depends following code-repos +1. rcloud-base/components/common : git@github.com:RafaySystems/rcloud-base.git +2. rcloud-base/components/adminsrv : git@github.com:RafaySystems/rcloud-base.git +3. rcloud-base/components/cluster-scheduler : git@github.com:RafaySystems/rcloud-base.git + +Check out the above code repos. + +Go to cluster-scheduler +``` +make teardown +make setup +make run +``` + +Go to adminsrv +``` +make teardown +make setup +GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info go run main.go + +``` + +### Rafay Relay Server +Get peer client token for relay. + +Open the admindb postgress and execute below SQL to fetch the token. +``` +admindb=# select token from sentry_bootstrap_agent_template where name='rafay-sentry-peering-client'; + token +---------------------- + brhsrbbipt349qt0qad0 + +``` + +Use above token set the environment variable for relay-server +``` +export RELAY_PEERING_TOKEN=brhsrbbipt349qt0qad0 +export RAFAY_RELAY_PEERSERVICE=http://peering.sentry.rafay.local:10001 +export POD_NAME=relay-pod1 +go run main.go --mode server --log-level 3 + +``` +### Rafay Relay Agent + +Get the config for rafay relay agent +``` +curl http://localhost:11000/infra/v3/project/:metadata.project/cluster/:metadata.name/download +``` + +Use the values of relay-agent-config and apply in the cluster. + +``` +kubectl -n rafay-system get cm/relay-agent-config -o yaml + +apiVersion: v1 +data: + clusterID: 4qkolkn + relays: '[{"token":"brht3cjipt35cb3kfjag","addr":"api.sentry.rafay.local:11000","endpoint":"*.core-connector.relay.rafay.local:443","name":"rafay-core-relay-agent","templateToken":"brhsrbbipt349qt0qae0"}]' +kind: ConfigMap +metadata: + creationTimestamp: "2020-04-28T07:10:53Z" + name: relay-agent-config + namespace: rafay-system + resourceVersion: "3127787" + selfLink: /api/v1/namespaces/rafay-system/configmaps/relay-agent-config + uid: e5fb5c16-c659-46d3-805f-3a2f3b73e9d7 +``` + +``` +kubectl apply -f +``` + +Run relay-agent +``` +export POD_NAME=relay-agent-pod1 +go run main.go --mode client --log-level 3 +``` + +Setup the local /etc/hosts file to resolve below hostname to localhost +``` +127.0.0.1 peering.sentry.rafay.local + +127.0.0.1 rx28oml.core-connector.relay.rafay.local +127.0.0.1 4qkolkn.core-connector.relay.rafay.local + +127.0.0.1 rx28oml.user.relay.rafay.local +127.0.0.1 4qkolkn.user.relay.rafay.local +``` + +Download kubeconfig +``` +curl http://localhost:11000/v2/sentry/kubeconfig/user?opts.selector=rafay.dev/clusterName=c-4 +``` + +Save the contents to a file and use for kubectl +``` +kubectl --v=6 --kubeconfig //kubeconfig-c-5 get pod -A +``` + + +### Production +Relay server need following environment variables + +POD_NAME - name of the relay instaance (auto generated) +POD_NAMESPACE - namespace of the relay instance + +RAFAY_SENTRY - URL to boostrap with sentry service +RAFAY_RELAY_PEERSERVICE - To join gRPC with sentry peer service + + +RELAY_USER_DOMAIN - domain suffix for relay user facing server +RELAY_CONNECTOR_DOMAIN - domain suffix for relay connector facing domain + +RELAY_TOKEN - Peer Service Token bootstrapping +RELAY_USER_TOKEN - Relay user facing server bootstrapping +RELAY_CONNECTOR_TOKEN - Relay connector facing server bootstrapping + +RELAY_LISTENIP - specify the listen IP if you want servers to listen other than 0.0.0.0 (usefull in testing local) + + +Examples: +``` +export RAFAY_SENTRY=http://sentry.rafay.dev:9000 +export RAFAY_RELAY_PEERSERVICE=http://peering.sentry.rafay.local:7001 + +export RELAY_USER_DOMAIN="user.relay.rafay.local" +export RELAY_CONNECTOR_DOMAIN="core-connector.relay.rafay.local" + +export RELAY_TOKEN=bqfvhabipt3a2g46986g +export RELAY_USER_TOKEN=bqfvhabipt3a2g46987g +export RELAY_CONNECTOR_TOKEN=bqfvhabipt3a2g469870 +export RELAY_LISTENIP=127.0.10.1 +``` \ No newline at end of file diff --git a/components/relay/go.mod b/components/relay/go.mod new file mode 100644 index 0000000..0bca88f --- /dev/null +++ b/components/relay/go.mod @@ -0,0 +1,114 @@ +module github.com/RafaySystems/rcloud-base/components/relay + +go 1.17 + +require ( + github.com/RafaySystems/rcloud-base/components/common v0.0.0-unpublished + github.com/cenkalti/backoff v2.2.1+incompatible + github.com/dgraph-io/ristretto v0.1.0 + github.com/felixge/httpsnoop v1.0.1 + github.com/felixge/tcpkeepalive v0.0.0-20160804073959-5bb0b2dea91e + github.com/fsnotify/fsnotify v1.5.1 + github.com/google/uuid v1.3.0 + github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b + github.com/jinzhu/inflection v1.0.0 + github.com/julienschmidt/httprouter v1.3.0 + github.com/rs/xid v1.3.0 + github.com/segmentio/encoding v0.3.3 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.8.1 + github.com/twmb/murmur3 v1.1.6 + go.uber.org/zap v1.20.0 + golang.org/x/net v0.0.0-20211209124913-491a49abca63 + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac + google.golang.org/grpc v1.43.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + k8s.io/api v0.23.2 + k8s.io/apimachinery v0.23.2 + k8s.io/client-go v0.23.2 + sigs.k8s.io/controller-runtime v0.11.0 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-openapi/analysis v0.20.1 // indirect + github.com/go-openapi/errors v0.20.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/loads v0.21.0 // indirect + github.com/go-openapi/runtime v0.21.1 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/strfmt v0.21.1 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-openapi/validate v0.20.3 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.7 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/processout/grpc-go-pool v1.2.1 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.28.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/valyala/fastjson v1.6.3 // indirect + go.mongodb.org/mongo-driver v1.7.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab // indirect + golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect + golang.org/x/text v0.3.7 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + k8s.io/apiextensions-apiserver v0.23.2 // indirect + k8s.io/component-base v0.23.2 // indirect + k8s.io/klog v1.0.0 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 // indirect + k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect + sigs.k8s.io/kustomize/pseudo/k8s v0.1.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect +) + +replace github.com/RafaySystems/rcloud-base/components/common v0.0.0-unpublished => /home/infracloud/development/vscode-workspace/rafay/rcloud-base/components/common/ diff --git a/components/relay/go.sum b/components/relay/go.sum new file mode 100644 index 0000000..93ffcef --- /dev/null +++ b/components/relay/go.sum @@ -0,0 +1,1301 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.2/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +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/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +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/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +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/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/tcpkeepalive v0.0.0-20160804073959-5bb0b2dea91e h1:mVIjvOd7NckIwf9J4hLB2YWXBYjhREF4vBeZXZ8mrWM= +github.com/felixge/tcpkeepalive v0.0.0-20160804073959-5bb0b2dea91e/go.mod h1:z0yk3Pix6k848RFizhkU4uY36ts5pB1t3toBwudGbBo= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= +github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= +github.com/go-openapi/analysis v0.20.1 h1:zdVbw8yoD4SWZeq+cWdGgquaB0W4VrsJvDJHJND/Ktc= +github.com/go-openapi/analysis v0.20.1/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= +github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= +github.com/go-openapi/loads v0.21.0 h1:jYtUO4wwP7psAweisP/MDoOpdzsYEESdoPcsWjHDR68= +github.com/go-openapi/loads v0.21.0/go.mod h1:rHYve9nZrQ4CJhyeIIFJINGCg1tQpx2yJrrNo8sf1ws= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= +github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= +github.com/go-openapi/runtime v0.21.1 h1:/KIG00BzA2x2HRStX2tnhbqbQdPcFlkgsYCiNY20FZs= +github.com/go-openapi/runtime v0.21.1/go.mod h1:aQg+kaIQEn+A2CRSY1TxbM8+sT9g2V3aLc1FbIAnbbs= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= +github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= +github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= +github.com/go-openapi/strfmt v0.20.2/go.mod h1:43urheQI9dNtE5lTZQfuFJvjYJKPrxicATpEfZwHUNk= +github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= +github.com/go-openapi/strfmt v0.21.1 h1:G6s2t5V5kGCHLVbSdZ/6lI8Wm4OzoPFkc3/cjAsKQrM= +github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= +github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= +github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= +github.com/go-openapi/validate v0.20.3 h1:GZPPhhKSZrE8HjB4eEkoYAZmoWA4+tCemSgINH1/vKw= +github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZXeftD16byIjA4= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/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/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM= +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/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3 h1:I8MsauTJQXZ8df8qJvEln0kYNc3bSapuaSsEsnFdEFU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.3/go.mod h1:lZdb/YAJUSj9OqrCHs2ihjtoO3+xK3G53wTYXFWRGDo= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE= +github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +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 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/processout/grpc-go-pool v1.2.1 h1:hbp1BOA02CIxEAoRLHGpUhhPFv77nwfBLBeO3Ya9P7I= +github.com/processout/grpc-go-pool v1.2.1/go.mod h1:F4hiNj96O6VQ87jv4rdz8R9tkHdelQQJ/J2B1a5VSt4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0 h1:vGVfV9KrDTvWt5boZO0I19g2E3CsWfpPPKZM9dt3mEw= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.3.3 h1:VG1HceOLwHkSDdkxshlu9pD4FftWkScmHc8RDQ+w9uM= +github.com/segmentio/encoding v0.3.3/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +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 v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= +github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/uptrace/bun v1.0.21/go.mod h1:u+QsgCgjGFwshy3euGAN1CLEO9RMf42lga5jQ/ezYsc= +github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc= +github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= +go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= +go.mongodb.org/mongo-driver v1.7.5 h1:ny3p0reEpgsR2cfA5cjgwFZg3Cv/ofFh/8jbhGtz9VI= +go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= +go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/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/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab h1:rfJ1bsoJQQIAoAxTxB7bme+vHrNkRw8CqfsYh9w54cw= +golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190125232054-d66bd3c5d5a6/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +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/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +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/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q= +google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= +k8s.io/api v0.23.2 h1:62cpzreV3dCuj0hqPi8r4dyWh48ogMcyh+ga9jEGij4= +k8s.io/api v0.23.2/go.mod h1:sYuDb3flCtRPI8ghn6qFrcK5ZBu2mhbElxRE95qpwlI= +k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= +k8s.io/apiextensions-apiserver v0.23.2 h1:N6CIVAhmF0ahgFKUMDdV/AUyckhUb4nIyVPohPtdUPk= +k8s.io/apiextensions-apiserver v0.23.2/go.mod h1:9cs7avT6+GfzbB0pambTvH11wcaR85QQg4ovl9s15UU= +k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= +k8s.io/apimachinery v0.23.2 h1:dBmjCOeYBdg2ibcQxMuUq+OopZ9fjfLIR5taP/XKeTs= +k8s.io/apimachinery v0.23.2/go.mod h1:zDqeV0AK62LbCI0CI7KbWCAYdLg+E+8UXJ0rIz5gmS8= +k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4= +k8s.io/apiserver v0.23.2/go.mod h1:Kdt8gafkPev9Gfh+H6lCPbmRu42f7BfhOfHKKa3dtyU= +k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= +k8s.io/client-go v0.23.2 h1:BNbOcxa99jxHH8mM1cPKGIrrKRnCSAfAtyonYGsbFtE= +k8s.io/client-go v0.23.2/go.mod h1:k3YbsWg6GWdHF1THHTQP88X9RhB1DWPo3Dq7KfU/D1c= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= +k8s.io/code-generator v0.23.2/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= +k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= +k8s.io/component-base v0.23.2 h1:dAYmUhWIBWO762etTjBEEKtYYHi5CoQInSLtK6LM1Zs= +k8s.io/component-base v0.23.2/go.mod h1:wS9Z03MO3oJ0RU8bB/dbXTiluGju+SC/F5i660gxB8c= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26 h1:2G24ndYyfk0l23ZrGutxb0s9TRe4m1ZjFlcu4cEU1zA= +k8s.io/kube-openapi v0.0.0-20220114203427-a0453230fd26/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20191030222137-2b95a09bc58d/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27/go.mod h1:tq2nT0Kx7W+/f2JVE+zxYtUhdjuELJkVpNz+x/QN5R4= +sigs.k8s.io/controller-runtime v0.11.0 h1:DqO+c8mywcZLFJWILq4iktoECTyn30Bkj0CwgqMpZWQ= +sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/kustomize/pseudo/k8s v0.1.0 h1:otg4dLFc03c3gzl+2CV8GPGcd1kk8wjXwD+UhhcCn5I= +sigs.k8s.io/kustomize/pseudo/k8s v0.1.0/go.mod h1:bl/gVJgYYhJZCZdYU2BfnaKYAlqFkgbJEkpl302jEss= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/components/relay/main.go b/components/relay/main.go new file mode 100644 index 0000000..dd666f8 --- /dev/null +++ b/components/relay/main.go @@ -0,0 +1,148 @@ +// Copyright (C) 2020 Rafay Systems https://rafay.co/ + +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "net/http" + _ "net/http/pprof" + + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/agent" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relay" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/tail" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" +) + +var ( + log *relaylogger.RelayLog +) + +func exit(cancel context.CancelFunc) { + cancel() + os.Exit(0) +} + +func terminate(cancel context.CancelFunc) { + cancel() + os.Exit(1) +} + +//os signal handler +func signalHandler(sig os.Signal, cancel context.CancelFunc) { + if sig == syscall.SIGINT || sig == syscall.SIGKILL || sig == syscall.SIGTERM || sig == syscall.SIGQUIT { + log.Error( + nil, + "Received", + "signal", sig, + ) + exit(cancel) + return + } + + log.Info( + "Received", + "signal", sig, + ) +} + +func main() { + + flag.String("mode", "server", "mode of relay, accepeted values server/client") + flag.Int("log-level", 1, "log leve, accepted values 0-3") + flag.Bool("profile", false, "enable profiling") + flag.Int("profile-port", 13000, "profiler http port") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + + mode := viper.GetString("mode") + logLevel := viper.GetInt("log-level") + profile := viper.GetBool("profile") + profilePort := viper.GetInt("profile-port") + + utils.GenUUID() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + log = relaylogger.NewLogger(logLevel).WithName("Relay") + + signalChan := make(chan os.Signal, 2) + signal.Notify(signalChan, + os.Interrupt, + syscall.SIGINT, + syscall.SIGHUP, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + + log.Info( + "Relay", + "mode", mode, + "loglevel", logLevel, + ) + + utils.LogLevel = logLevel + + if profile { + go func() { + log.Info("profiling enabled", "profilePort", profilePort) + err := http.ListenAndServe(fmt.Sprintf(":%d", profilePort), nil) + if err != nil { + panic(err) + } + }() + } + +restart: + switch mode { + case "client": + log.Info("Starting relay agent client") + utils.Mode = utils.RELAYAGENT + go agent.RunRelayKubeCTLAgent(ctx, logLevel) + case "cdclient": + log.Info("Starting relay agent client") + utils.Mode = utils.CDRELAYAGENT + go agent.RunRelayCDAgent(ctx, logLevel) + case "tail": + log.Info("Starting relay log tail") + go tail.RunRelayTail(ctx) + case "cdrelay": + utils.Mode = utils.CDRELAY + log.Info("Starting cdrelay server") + go relay.RunCDRelayServer(ctx, logLevel) + default: + // default server + utils.Mode = utils.RELAY + log.Info("Starting relay server") + go relay.RunRelayServer(ctx, logLevel) + } + + for { + select { + case <-utils.ExitChan: + log.Info( + "got exit", + ) + time.Sleep(time.Second * 5) + goto restart + case <-utils.TerminateChan: + terminate(cancel) + case sig := <-signalChan: + signalHandler(sig, cancel) + } + } +} diff --git a/components/relay/pkg/agent/agent.go b/components/relay/pkg/agent/agent.go new file mode 100644 index 0000000..705ce21 --- /dev/null +++ b/components/relay/pkg/agent/agent.go @@ -0,0 +1,595 @@ +package agent + +import ( + "context" + "crypto/sha256" + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "strconv" + "time" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/apply" + clientutil "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/client" + "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/register" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/cleanup" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/proxy" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/tunnel" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + podNameEnv = "POD_NAME" + podNamespaceEnv = "POD_NAMESPACE" + maxDialsEnv = "MAX_DIALS" + dialoutProxyEnv = "DIALOUT_PROXY" + dialoutProxyAuthEnv = "DIALOUT_PROXY_AUTHENTICATION" // user:password + configNameEnv = "RELAY_CONFIGMAP_NAME" + configNameCD = "cd-agent-configmap" + cdConfigPath = "/etc/config/relayConfigData" + relayRegisterTokenName = "relays" + relayClusterIDName = "clusterID" + relayAgentIDName = "agentID" +) + +var ( + podName string + podNamespace string + relayRegisterToken string + relayClusterID string + relayAgentID string + applier client.Client + lastHash string + log *relaylogger.RelayLog + configName string +) + +const ( + rafaySystemNamespace = "rafay-system" +) + +func processRelayConfigData(cfgData string) error { + if err := json.Unmarshal([]byte(cfgData), &utils.RelayNetworks); err != nil { + log.Error( + err, + "failed to unmashal", + ) + return err + } + return nil +} + +func getConfigMap(ctx context.Context, c client.Client, name string) (*corev1.ConfigMap, error) { + cm := corev1.ConfigMap{} + + err := c.Get(ctx, client.ObjectKey{ + Namespace: rafaySystemNamespace, + Name: name, + }, &cm) + if err != nil { + return nil, err + } + + cm.TypeMeta = metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + } + + return &cm, nil +} + +func getConfigHash(ctx context.Context, configMap string) (string, error) { + + cm, err := getConfigMap(ctx, applier, configMap) + if err != nil { + return "", err + } + + b, err := json.Marshal(cm.Data) + if err != nil { + return "", err + } + + h := sha256.New() + h.Write(b) + + //return cm.Annotations[hash.ObjectHash], nil + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + +func getRelayAgentConfig(ctx context.Context) error { + var ok bool + + cm, err := getConfigMap(ctx, applier, configName) + if err != nil { + return err + } + + if relayClusterID, ok = cm.Data[relayClusterIDName]; !ok { + return fmt.Errorf("failed to get relayClusterID") + } + if relayRegisterToken, ok = cm.Data[relayRegisterTokenName]; !ok { + return fmt.Errorf("failed to get relayRegisterToken") + } + + if err := processRelayConfigData(relayRegisterToken); err != nil { + return fmt.Errorf("failed to process relayRegisterToken %s", relayRegisterToken) + } + if len(utils.RelayNetworks) <= 0 { + return fmt.Errorf("empty relaynetwork configmap %s", relayRegisterToken) + } + + log.Info( + "relay agent config values", + "podName", podName, + "podNamespace", podNamespace, + ) + + for _, item := range utils.RelayNetworks { + log.Info( + "relay network info", + "Token", item.Token, + "Addr", item.Addr, + "Domain", item.Domain, + "Name", item.Name, + ) + } + + utils.ClusterID = relayClusterID + return nil +} + +func setupclient(ctx context.Context, log *relaylogger.RelayLog) error { + configName = "relay-agent-config" + viper.SetDefault(podNameEnv, "relay-agent") + viper.SetDefault(podNamespaceEnv, "rafay-system") + viper.BindEnv(podNameEnv) + viper.BindEnv(podNamespaceEnv) + + podName = viper.GetString(podNameEnv) + podNamespace = viper.GetString(podNamespaceEnv) + + utils.PODNAME = podName + + viper.SetDefault(maxDialsEnv, "10") + viper.BindEnv(maxDialsEnv) + utils.MaxDials = viper.GetInt(maxDialsEnv) + + viper.SetDefault(dialoutProxyEnv, "") + viper.BindEnv(dialoutProxyEnv) + + if u, err := url.Parse(viper.GetString(dialoutProxyEnv)); err == nil { + utils.DialoutProxy = u.Host + } else { + utils.DialoutProxy = viper.GetString(dialoutProxyEnv) + } + + viper.SetDefault(dialoutProxyAuthEnv, "") + viper.BindEnv(dialoutProxyAuthEnv) + proxyAuth := viper.GetString(dialoutProxyAuthEnv) + utils.DialoutProxyAuth = proxyAuth + + if utils.MaxDials < utils.MinDials { + utils.MaxDials = utils.MinDials + } + + viper.SetDefault(configNameEnv, "") + viper.BindEnv(configNameEnv) + customeRelayCfgMap := viper.GetString(configNameEnv) + if customeRelayCfgMap != "" { + configName = customeRelayCfgMap + } + + err := getRelayAgentConfig(ctx) + if err != nil { + log.Error( + err, + "failed to get relay config", + ) + return err + } + return nil +} + +func setupCDClient(ctx context.Context, log *relaylogger.RelayLog) error { + + viper.SetDefault(podNameEnv, "relay-cdagent") + viper.SetDefault(podNamespaceEnv, "rafay-system") + viper.BindEnv(podNameEnv) + viper.BindEnv(podNamespaceEnv) + + podName = viper.GetString(podNameEnv) + podNamespace = viper.GetString(podNamespaceEnv) + + utils.PODNAME = podName + + viper.SetDefault(maxDialsEnv, "2") + viper.BindEnv(maxDialsEnv) + // utils.MaxDials = viper.GetInt(maxDialsEnv) + + viper.SetDefault(dialoutProxyEnv, "") + viper.BindEnv(dialoutProxyEnv) + // utils.DialoutProxy = viper.GetString(dialoutProxyEnv) + + viper.SetDefault(dialoutProxyAuthEnv, "") + viper.BindEnv(dialoutProxyAuthEnv) + // proxyAuth := viper.GetString(dialoutProxyAuthEnv) + // utils.DialoutProxyAuth = base64.StdEncoding.EncodeToString([]byte(proxyAuth)) + + // if utils.MaxDials < 1 { + // utils.MaxDials = 2 + // } + + err := getRelayCDAgentConfig(ctx) + if err != nil { + log.Error( + err, + "failed to get relay config", + ) + return err + } + return nil +} + +func getRelayCDAgentConfig(ctx context.Context) error { + var ok bool + + configBytes, err := ioutil.ReadFile(cdConfigPath) + if err != nil { + return err + } + + var config map[string]interface{} + err = json.Unmarshal(configBytes, &config) + if err != nil { + return err + } + + if relayAgentID, ok = config[relayAgentIDName].(string); !ok { + return fmt.Errorf("failed to get cd-relayClusterID") + } + + if maxDialsStr, ok := config["maxDials"].(string); ok { + maxDials, err := strconv.Atoi(maxDialsStr) + if err != nil { + maxDials = 2 + } + utils.MaxDials = maxDials + } + + if utils.MaxDials < utils.MinDials { + utils.MaxDials = utils.MinDials + } + + var dialoutProxy string + if dp, ok := config["dialoutProxy"]; ok { + if dialoutProxy, ok = dp.(string); ok { + } + } + + if dialoutProxy == "" { + if u, err := url.Parse(viper.GetString(dialoutProxyEnv)); err == nil { + dialoutProxy = u.Host + } else { + dialoutProxy = viper.GetString(dialoutProxyEnv) + } + } + + utils.DialoutProxy = dialoutProxy + + var dialoutProxyAuth string + if dpa, ok := config["dialoutProxyAuthentication"]; ok { + if dialoutProxyAuth, ok = dpa.(string); ok { + } + } + + if dialoutProxyAuth == "" { + dialoutProxyAuth = viper.GetString(dialoutProxyAuthEnv) + } + + utils.DialoutProxyAuth = dialoutProxyAuth + + if relays, ok := config[relayRegisterTokenName].(interface{}); ok { + relayRegisterTokenBytes, err := json.Marshal(relays) + if err != nil { + return fmt.Errorf("failed to get cd-relayRegisterToken: %s", err.Error()) + } + relayRegisterToken = string(relayRegisterTokenBytes) + } else { + return fmt.Errorf("failed to get cd-relayRegisterToken") + } + + if err := processRelayConfigData(relayRegisterToken); err != nil { + return fmt.Errorf("failed to process cd-relayRegisterToken %s", relayRegisterToken) + } + if len(utils.RelayNetworks) <= 0 { + return fmt.Errorf("empty cd-relaynetwork configmap %s", relayRegisterToken) + } + + log.Info( + "relay cd agent config values", + "podName", podName, + "podNamespace", podNamespace, + ) + + for _, item := range utils.RelayNetworks { + log.Info( + "relay network info", + "Token", item.Token, + "Addr", item.Addr, + "Domain", item.Domain, + "Name", item.Name, + ) + } + + utils.AgentID = relayAgentID + return nil +} + +// registerRelayAgent will register with rafay-core-relay-connector template token +// registration fetches client-certificate/root-ca to connect to relay server +func registerRelayAgent(ctx context.Context, rn utils.Relaynetwork) error { + cfg := ®ister.Config{ + TemplateToken: rn.TemplateToken, + Addr: rn.Addr, + ClientID: rn.Token, + ClientIP: utils.GetRelayIP(), + Name: podName, + } + + if utils.IsHTTPS(rn.Addr) { + cfg.Scheme = "https" + } + + log.Info("config:", cfg) + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register relay agent", + ) + return err + } + + log.Info( + "certificate:", + string(cfg.Certificate), + ) + + rc := utils.RelayNetworkConfig{} + rc.Network = rn + rc.RelayAgentCACert = cfg.CACertificate + rc.RelayAgentCert = cfg.Certificate + rc.RelayAgentKey = cfg.PrivateKey + + utils.RelayAgentConfig[rn.Name] = rc + + return nil +} + +func checkConfigMapChange(configMap string) bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + h, err := getConfigHash(ctx, configMap) + if err != nil { + log.Error( + err, + "failed to get relay configmap hash", + ) + return false + } + + if lastHash != "" { + if lastHash != h { + //hash changed + log.Info("hash of configmap data changed", "prev", lastHash, "curr", h) + lastHash = h + return true + } + } else { + log.Info("setting hash of configmap data", "hash", h) + lastHash = h + } + + return false +} + +// RunRelayCDAgent entry to the relay client +func RunRelayCDAgent(ctx context.Context, logLvl int) { + lastHash = "" + utils.RelayNetworks = nil + utils.RelayAgentConfig = make(map[string]utils.RelayNetworkConfig) + + log = relaylogger.NewLogger(logLvl).WithName("Relay Agent") + + c, err := clientutil.New() + if err != nil { + log.Error( + err, + "failed in clientutil new", + ) + utils.ExitChan <- true + return + } + + if err := proxy.InitUnixCacheRoundTripper(); err != nil { + log.Error( + err, + "failed to init unix cached round tripper", + ) + utils.ExitChan <- true + return + } + + applier = apply.NewApplier(c) + + rctx, cancel := context.WithCancel(ctx) + defer cancel() + + if err := setupCDClient(rctx, log); err != nil { + log.Error( + err, + "relay agent exiting", + ) + utils.ExitChan <- true + return + } + + for _, rn := range utils.RelayNetworks { + go handleRelayNetworks(rctx, rn) + } + + //watch config changes + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error(err, "unable to create watcher for config file") + utils.ExitChan <- true + return + } + defer watcher.Close() + err = watcher.Add(cdConfigPath) + if err != nil { + log.Error(err, "unable to add config file to watcher") + } + for { + select { + case event, ok := <-watcher.Events: + if ok { + log.Info("event:", event, "modified file:", event.Name) + } + if event.Op != fsnotify.Chmod { + utils.ExitChan <- true + return + } + case err, ok := <-watcher.Errors: + if !ok { + utils.ExitChan <- true + return + } + log.Info("error:", err) + case <-ctx.Done(): + log.Info( + "Relay agent exiting", + ) + return + } + } + +} + +// RunRelayKubeCTLAgent entry to the relay client +func RunRelayKubeCTLAgent(ctx context.Context, logLvl int) { + lastHash = "" + utils.RelayNetworks = nil + utils.RelayAgentConfig = make(map[string]utils.RelayNetworkConfig) + + log = relaylogger.NewLogger(logLvl).WithName("Relay Agent") + + c, err := clientutil.New() + if err != nil { + log.Error( + err, + "failed in clientutil new", + ) + utils.TerminateChan <- true + return + } + + if err := proxy.InitUnixCacheRoundTripper(); err != nil { + log.Error( + err, + "failed to init unix cached round tripper", + ) + utils.ExitChan <- true + return + } + + applier = apply.NewApplier(c) + + rctx, cancel := context.WithCancel(ctx) + defer cancel() + + if err := setupclient(rctx, log); err != nil { + log.Error( + err, + "relay agent exiting", + ) + utils.ExitChan <- true + return + } + + go cleanup.StaleAuthz(rctx) + + for _, rn := range utils.RelayNetworks { + go handleRelayNetworks(rctx, rn) + } + + //ticker configmap changes + ticker := time.NewTicker(10 * time.Second) + for { + select { + case <-ticker.C: + if checkConfigMapChange(configName) { + // cancel the ctx + ticker.Stop() + utils.ExitChan <- true + return + } + case <-ctx.Done(): + log.Info( + "Relay agent exiting", + ) + return + } + } + +} + +func handleRelayNetworks(ctx context.Context, rn utils.Relaynetwork) { + rnExitChan := make(chan bool) +restartRegister: + ticker := time.NewTicker(5 * time.Second) + if err := registerRelayAgent(ctx, rn); err != nil { + //wait until config is available + for { + select { + case <-ticker.C: + goto restartRegister + case <-ctx.Done(): + ticker.Stop() + return + } + } + } + ticker.Stop() + +restartNetwork: + go tunnel.StartClient(ctx, log, "", utils.RelayAgentConfig[rn.Name], rnExitChan) + + for { + select { + case <-rnExitChan: + log.Info( + "relay network stopped, restart in 5 secs", + "name", rn.Name, + ) + time.Sleep(2 * time.Second) + goto restartNetwork + case <-ctx.Done(): + log.Info( + "Relay network exiting", + "name", rn.Name, + ) + return + } + } + +} diff --git a/components/relay/pkg/agent/lib.go b/components/relay/pkg/agent/lib.go new file mode 100644 index 0000000..65e84ad --- /dev/null +++ b/components/relay/pkg/agent/lib.go @@ -0,0 +1,36 @@ +package agent + +import ( + "context" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" +) + +// RunCDAgentRoutine starts cd agent. run as a go routine +func RunCDAgentRoutine(ctx context.Context, logLevel int) { + + _log := relaylogger.NewLogger(logLevel).WithName("Relay") + _log.Info("Starting relay agent client") + + utils.GenUUID() + utils.LogLevel = logLevel + utils.Mode = utils.CDRELAYAGENT + +restart: + go RunRelayCDAgent(ctx, logLevel) + + for { + select { + case <-utils.ExitChan: + log.Info( + "got exit", + ) + time.Sleep(time.Second * 5) + goto restart + case <-ctx.Done(): + return + } + } +} diff --git a/components/relay/pkg/audit/audit.go b/components/relay/pkg/audit/audit.go new file mode 100644 index 0000000..10de238 --- /dev/null +++ b/components/relay/pkg/audit/audit.go @@ -0,0 +1,97 @@ +package audit + +import ( + "net/http" + "strings" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/sessions" + "github.com/felixge/httpsnoop" + "github.com/rs/xid" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// options holds audit options +type options struct { + basePath string + maxSizeMB int + maxBackups int + maxAgeDays int +} + +// Option is the functional argument for audit options +type Option func(o *options) + +// WithBasePath sets base path for audit logs +func WithBasePath(path string) Option { + return func(o *options) { + o.basePath = path + } +} + +// WithMaxSizeMB sets max size of audit file before it is rotated +func WithMaxSizeMB(size int) Option { + return func(o *options) { + o.maxSizeMB = size + } +} + +// WithMaxBackups sets maximum number of backed up rotated audit logs to maintain +func WithMaxBackups(backups int) Option { + return func(o *options) { + o.maxBackups = backups + } +} + +// WithMaxAgeDays sets age after which backed up rotated audit logs will be deleted +func WithMaxAgeDays(ageDays int) Option { + return func(o *options) { + o.maxAgeDays = ageDays + } +} + +type audit struct { + logger *zap.Logger +} + +func (a *audit) handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + metrics := httpsnoop.CaptureMetrics(next, w, r) + + fields := make([]zapcore.Field, 0, 10) + fields = append(fields, zap.String("xid", xid.New().String())) + fields = append(fields, zap.String("method", r.Method)) + fields = append(fields, zap.String("url", r.URL.EscapedPath())) + fields = append(fields, zap.String("query", r.URL.RawQuery)) + fields = append(fields, zap.String("serverName", r.TLS.ServerName)) + fields = append(fields, zap.String("user", r.TLS.PeerCertificates[0].Subject.CommonName)) + fields = append(fields, zap.String("remoteAddr", strings.Split(r.RemoteAddr, ":")[0])) + fields = append(fields, zap.Int("statusCode", metrics.Code)) + fields = append(fields, zap.Int64("written", metrics.Written)) + fields = append(fields, zap.Duration("duration", metrics.Duration)) + if r.Header.Get("X-Rafay-Audit") == "" { + a.logger.Info("access", fields...) + } else { + a.logger.Info("audit", fields...) + } + + if metrics.Code == http.StatusUnauthorized || metrics.Code == http.StatusBadGateway { + sessKey := r.Header.Get("X-Rafay-Sessionkey") + sessions.SetSessionErrorFlag(sessKey) + } + + }) +} + +// WrapWithAudit wrap audit handler around next http.Handler +func WrapWithAudit(next http.Handler, opts ...Option) http.Handler { + + auditOpts := &options{} + for _, opt := range opts { + opt(auditOpts) + } + + audit := &audit{logger: getAuditLogger(auditOpts)} + return audit.handler(next) +} diff --git a/components/relay/pkg/audit/logger.go b/components/relay/pkg/audit/logger.go new file mode 100644 index 0000000..49eda82 --- /dev/null +++ b/components/relay/pkg/audit/logger.go @@ -0,0 +1,30 @@ +package audit + +import ( + "fmt" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +func getAuditLogger(opts *options) *zap.Logger { + + path := fmt.Sprintf("%s/audit.log", opts.basePath) + + encoder := zap.NewProductionEncoderConfig() + encoder.EncodeTime = zapcore.ISO8601TimeEncoder + + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoder), + zapcore.AddSync(&lumberjack.Logger{ + Filename: path, + MaxSize: opts.maxSizeMB, // megabytes + MaxBackups: opts.maxBackups, + MaxAge: opts.maxAgeDays, // days + }), + zap.InfoLevel, + )) + + return logger +} diff --git a/components/relay/pkg/cleanup/authz.go b/components/relay/pkg/cleanup/authz.go new file mode 100644 index 0000000..f4d7d3d --- /dev/null +++ b/components/relay/pkg/cleanup/authz.go @@ -0,0 +1,150 @@ +package cleanup + +import ( + "context" + "strconv" + "time" + + clientutil "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/client" + "github.com/RafaySystems/rcloud-base/components/common/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/client" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" +) + +var ( + _log = log.GetLogger() +) + +const ( + // how often should the cleanup routine run + sweepInterval = time.Minute * 10 + // any authz which is olderThan this should be cleaned up + olderThan = time.Hour * 8 + + rafayRelayLabel = "rafay-relay" + authzRefreshLabel = "authz-refreshed" + rafaySystemNS = "rafay-system" + + maxResults = 50 +) + +func getMatchingLabelSelector() (sel client.MatchingLabelsSelector, err error) { + var relayLabelReq, refreshLabelReq *labels.Requirement + + relayLabelReq, err = labels.NewRequirement(rafayRelayLabel, selection.Equals, []string{"true"}) + if err != nil { + _log.Infow("unable to create relay label requirment", "error", err) + return + } + + refreshLabelReq, err = labels.NewRequirement( + authzRefreshLabel, + selection.LessThan, + []string{strconv.FormatInt(time.Now().Add(-olderThan).Unix(), 10)}, + ) + if err != nil { + _log.Infow("unable to create authz refresh requirment", "error", err) + return + } + sel.Selector = labels.NewSelector().Add(*relayLabelReq, *refreshLabelReq) + + return +} + +func cleanServiceAccounts(ctx context.Context, c client.Client, sel client.MatchingLabelsSelector) { + nCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + var saList corev1.ServiceAccountList + + err := c.List(nCtx, &saList, sel, client.InNamespace(rafaySystemNS), client.Limit(maxResults)) + if err != nil { + _log.Infow("unable to list service accounts", "error", err) + return + } + + for _, item := range saList.Items { + _log.Infow("found stale service account", "name", item.Name) + err = c.Delete(ctx, &item) + if err != nil { + _log.Infow("unable to delete stale service account", "name", item.Name, "error", err) + } + } + +} + +func cleanClusterRoleBindings(ctx context.Context, c client.Client, sel client.MatchingLabelsSelector) { + nCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + var crbList rbacv1.ClusterRoleBindingList + + err := c.List(nCtx, &crbList, sel, client.Limit(maxResults)) + if err != nil { + _log.Infow("unable to list cluster role bindings", "error", err) + return + } + + for _, item := range crbList.Items { + _log.Infow("found stale cluster role binding", "name", item.Name) + err = c.Delete(ctx, &item) + if err != nil { + _log.Infow("unable to delete cluster role binding", "name", item.Name, "error", err) + } + } +} + +func cleanRoleBindings(ctx context.Context, c client.Client, sel client.MatchingLabelsSelector) { + nCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + var rbList rbacv1.RoleBindingList + + err := c.List(nCtx, &rbList, sel, client.Limit(maxResults)) + if err != nil { + _log.Infow("unable to list role bindings", "error", err) + return + } + + for _, item := range rbList.Items { + _log.Infow("found stale role binding", "name", item.Name) + err = c.Delete(ctx, &item) + if err != nil { + _log.Infow("unable to delete role binding", "name", item.Name, "error", err) + } + } +} + +// StaleAuthz cleans up state authorizations provisioned in the cluster +func StaleAuthz(ctx context.Context) { + c, err := clientutil.New() + if err != nil { + _log.Fatalw("unable to create client for cleaning stale authz entries", "error", err) + } + + ticker := time.NewTicker(sweepInterval) +sweepLoop: + for ; true; <-ticker.C { + select { + case <-ctx.Done(): + break sweepLoop + default: + } + + sel, err := getMatchingLabelSelector() + if err != nil { + _log.Infow("unable to build matching label selector", "error", err) + continue + } + + _log.Infow("finding authz matching", "selector", sel) + + cleanClusterRoleBindings(ctx, c, sel) + cleanRoleBindings(ctx, c, sel) + cleanServiceAccounts(ctx, c, sel) + } +} diff --git a/components/relay/pkg/profile/profile.go b/components/relay/pkg/profile/profile.go new file mode 100644 index 0000000..b9b3d71 --- /dev/null +++ b/components/relay/pkg/profile/profile.go @@ -0,0 +1,31 @@ +package profile + +import ( + "context" + "fmt" + "net/http" + _ "net/http/pprof" + "time" +) + +// RunProfiler runs pprof on the given port +func RunProfiler(port int, stop <-chan struct{}) error { + + s := http.Server{ + Addr: fmt.Sprintf(":%d", port), + } + + defer func() { + <-stop + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + s.Shutdown(ctx) + }() + + err := s.ListenAndServe() + if err != nil { + return err + } + + return nil +} diff --git a/components/relay/pkg/proxy/kube_proxy.go b/components/relay/pkg/proxy/kube_proxy.go new file mode 100644 index 0000000..0e6b786 --- /dev/null +++ b/components/relay/pkg/proxy/kube_proxy.go @@ -0,0 +1,299 @@ +package proxy + +import ( + "context" + "crypto/tls" + "encoding/json" + "hash" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/dgraph-io/ristretto" + utilnet "k8s.io/apimachinery/pkg/util/net" + k8proxy "k8s.io/apimachinery/pkg/util/proxy" + "k8s.io/client-go/rest" + "k8s.io/client-go/transport" +) + +// TODO convert to JSON +type responder struct{} + +func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) +} + +type unixCachedRoundTrip struct { + proxy http.Handler + t *http.Transport +} + +type relayCachedPeerRoundTrip struct { + proxy http.Handler + t *http.Transport +} + +var ( + cacheTTL = time.Minute * 15 + // cache map of unix round tripper + unixCachedRoundTripper *ristretto.Cache + // cache map for peer upstream round tripper + relayPeerRoundTripper *ristretto.Cache +) + +// InitUnixCacheRoundTripper initialize the cache +func InitUnixCacheRoundTripper() error { + var err error + unixCachedRoundTripper, err = ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 25, // Maximum cost of cache (32MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + + return err +} + +// InitPeerCacheRoundTripper initialize the cache +func InitPeerCacheRoundTripper() error { + var err error + relayPeerRoundTripper, err = ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 25, // Maximum cost of cache (32MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + + return err +} + +// makeUpgradeTransport creates a transport that explicitly bypasses HTTP2 support +// for proxy connections that must upgrade. +func makeUpgradeTransport(config *rest.Config, keepalive time.Duration) (k8proxy.UpgradeRequestRoundTripper, error) { + + transportConfig, err := config.TransportConfig() + if err != nil { + return nil, err + } + tlsConfig, err := transport.TLSConfigFor(transportConfig) + if err != nil { + return nil, err + } + tlsConfig.NextProtos = []string{"http/1.1"} + rt := utilnet.SetOldTransportDefaults(&http.Transport{ + TLSClientConfig: tlsConfig, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: keepalive, + }).DialContext, + TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + MaxIdleConns: idleConns, + MaxConnsPerHost: idleConnsPerHost, + }) + + upgrader, err := transport.HTTPWrappersForConfig(transportConfig, k8proxy.MirrorRequest) + if err != nil { + return nil, err + } + + return k8proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil +} + +// NewCachedKubeHandler is cached kube handler +func NewCachedKubeHandler(cfg *rest.Config, keepalive time.Duration) (*k8proxy.UpgradeAwareHandler, error) { + host := cfg.Host + if !strings.HasSuffix(host, "/") { + host = host + "/" + } + target, err := url.Parse(host) + if err != nil { + return nil, err + } + + responder := &responder{} + + transport, err := rest.TransportFor(cfg) + if err != nil { + return nil, err + } + + rt, err := NewCachedRoundTripper(transport) + if err != nil { + return nil, err + } + + upgradeTransport, err := makeUpgradeTransport(cfg, keepalive) + if err != nil { + return nil, err + } + + kproxy := k8proxy.NewUpgradeAwareHandler(target, rt, false, false, responder) + kproxy.UpgradeTransport = upgradeTransport + kproxy.UseRequestLocation = true + + return kproxy, nil +} + +// UnixDialContext unix dial +func UnixDialContext(sockPath, key, username, sni string) func(ctx context.Context, network, addr string) (net.Conn, error) { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + + conn, err := net.DialTimeout("unix", sockPath, 60*time.Second) + if err != nil { + return nil, err + } + + hdr := &utils.ProxyProtocolMessage{ + DialinKey: key, + UserName: username, + SNI: sni, + } + + b, err := json.Marshal(hdr) + if err != nil { + return nil, err + } + + buf := make([]byte, utils.ProxyProtocolSize) + copy(buf, b) + + _, err = conn.Write(buf) + if err != nil { + return nil, err + } + return conn, nil + } +} + +func makeUnixUpgradeTransport(sockPath, key, username, sni string) (k8proxy.UpgradeRequestRoundTripper, error) { + + rt := utilnet.SetOldTransportDefaults(&http.Transport{ + DialContext: UnixDialContext(sockPath, key, username, sni), + }) + + upgrader, err := transport.HTTPWrappersForConfig(new(transport.Config), k8proxy.MirrorRequest) + if err != nil { + return nil, err + } + return k8proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil +} + +func getRTCacheKey(skey string) (key uint64) { + hasher := _hashPool.Get().(hash.Hash64) + hasher.Reset() + hasher.Write([]byte(skey)) + key = hasher.Sum64() + _hashPool.Put(hasher) + return +} + +type unixHandlerOptions struct { + sockPath string + key string + username string + sni string +} + +//UnixKubeHandler unix handler +func UnixKubeHandler(sockPath, key, username, sni string) (http.Handler, error) { + + hkey := getRTCacheKey(username + key) + if val, ok := unixCachedRoundTripper.Get(hkey); ok { + rt := val.(unixCachedRoundTrip) + return rt.proxy, nil + } + + target, err := url.Parse("http://unixpath-relay/") + if err != nil { + return nil, err + } + + responder := &responder{} + + upgradeTransport, err := makeUnixUpgradeTransport(sockPath, key, username, sni) + if err != nil { + return nil, err + } + + t := getUnixTransport(sockPath, key, username, sni) + proxy := k8proxy.NewUpgradeAwareHandler(target, t, false, false, responder) + proxy.UpgradeTransport = upgradeTransport + proxy.UseRequestLocation = true + + rt := unixCachedRoundTrip{ + proxy: proxy, + t: t, + } + + unixCachedRoundTripper.SetWithTTL(hkey, rt, 100, cacheTTL) + + return proxy, nil +} + +// Peer Relay Proxy Helpers +func peerDialContext(relayIP string) func(ctx context.Context, network, addr string) (net.Conn, error) { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + conn, err := net.DialTimeout("tcp", relayIP, 60*time.Second) + if err != nil { + return nil, err + } + return conn, nil + } +} + +//makePeerUpgradeTransport ... +func makePeerUpgradeTransport(relayIP string, tlscfg *tls.Config) (k8proxy.UpgradeRequestRoundTripper, error) { + rt := utilnet.SetOldTransportDefaults(&http.Transport{ + DialContext: peerDialContext(relayIP), + TLSClientConfig: tlscfg, + MaxIdleConns: 10, + IdleConnTimeout: utils.IdleTimeout, + TLSHandshakeTimeout: 30 * time.Second, + // Keep the peer upstream non http2 + // Found issues in upgradeaware http2 + // missing http2 preface when doing proxy. + ForceAttemptHTTP2: false, + }) + + upgrader, err := transport.HTTPWrappersForConfig(new(transport.Config), k8proxy.MirrorRequest) + if err != nil { + return nil, err + } + return k8proxy.NewUpgradeRequestRoundTripper(rt, upgrader), nil +} + +//PeerKubeHandler peer proxying handler +func PeerKubeHandler(tlscfg *tls.Config, relayIP string) (http.Handler, error) { + hkey := getRTCacheKey(tlscfg.ServerName) + if val, ok := relayPeerRoundTripper.Get(hkey); ok { + rt := val.(relayCachedPeerRoundTrip) + return rt.proxy, nil + } + + target, err := url.Parse("https://" + tlscfg.ServerName + "/") + if err != nil { + return nil, err + } + + responder := &responder{} + + upgradeTransport, err := makePeerUpgradeTransport(relayIP, tlscfg) + if err != nil { + return nil, err + } + + t := getPeerTransport(tlscfg, relayIP) + proxy := k8proxy.NewUpgradeAwareHandler(target, t, false, false, responder) + proxy.UpgradeTransport = upgradeTransport + proxy.UseRequestLocation = true + + rt := relayCachedPeerRoundTrip{ + proxy: proxy, + t: t, + } + + relayPeerRoundTripper.SetWithTTL(hkey, rt, 100, cacheTTL) + + return proxy, nil +} diff --git a/components/relay/pkg/proxy/proxy.go b/components/relay/pkg/proxy/proxy.go new file mode 100644 index 0000000..c760431 --- /dev/null +++ b/components/relay/pkg/proxy/proxy.go @@ -0,0 +1,12 @@ +package proxy + +import ( + "io" + "net/http" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" +) + +// Func is responsible for forwarding a remote connection to local server +// and writing the response. +type Func func(w io.Writer, r io.ReadCloser, msg *utils.ControlMessage, req *http.Request) diff --git a/components/relay/pkg/proxy/round_tripper.go b/components/relay/pkg/proxy/round_tripper.go new file mode 100644 index 0000000..2bf5205 --- /dev/null +++ b/components/relay/pkg/proxy/round_tripper.go @@ -0,0 +1,214 @@ +package proxy + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "hash" + "net/http" + "sync" + "time" + + "github.com/twmb/murmur3" + + clientutil "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/client" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/dgraph-io/ristretto" + utilnet "k8s.io/apimachinery/pkg/util/net" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + logger = relaylogger.NewLogger(utils.LogLevel).WithName("Proxy") + // ErrInvalidUser is returned for invalid user + ErrInvalidUser = errors.New("invalid user") +) + +var _hashPool = sync.Pool{ + New: func() interface{} { + // The Pool's New function should generally only return pointer + // types, since a pointer can be put into the return interface + // value without an allocation: + return murmur3.New64() + }, +} + +const ( + defaulTLSHandshakeTimeout = 10 * time.Second + idleConnsPerHost = 5 + idleConns = 25 + defaultTimeout = 30 * time.Second + defaultKeepAlive = 30 * time.Second + disableCompression = false +) + +func getUnixTransport(sockPath, key, username, sni string) *http.Transport { + return utilnet.SetTransportDefaults(&http.Transport{ + TLSHandshakeTimeout: defaulTLSHandshakeTimeout, + MaxIdleConnsPerHost: idleConnsPerHost, + DialContext: UnixDialContext(sockPath, key, username, sni), + DisableCompression: disableCompression, + MaxIdleConns: idleConns, + }) +} + +func getPeerTransport(tlscfg *tls.Config, relayIP string) *http.Transport { + if tlscfg != nil { + tlscfg.NextProtos = []string{"http/1.1"} + } + return &http.Transport{ + TLSHandshakeTimeout: defaulTLSHandshakeTimeout, + TLSClientConfig: tlscfg, + MaxIdleConns: idleConns, + MaxIdleConnsPerHost: idleConnsPerHost, + DialContext: peerDialContext(relayIP), + DisableCompression: disableCompression, + TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + //Keep the peer upstream non http2 + ForceAttemptHTTP2: false, + } +} + +// NewCachedRoundTripper returns new cached round tripper +func NewCachedRoundTripper(rt http.RoundTripper) (http.RoundTripper, error) { + logger = relaylogger.NewLogger(utils.LogLevel).WithName("CachedRoundTripper") + c, err := clientutil.New() + if err != nil { + logger.Error( + err, + "failed in clientutil.New", + ) + return nil, err + } + + cache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 25, // Maximum cost of cache (32MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + if err != nil { + return nil, err + } + + crt := &cachedRoundTripper{client: c, cache: cache, rt: rt} + + return crt, nil +} + +type cachedRoundTripper struct { + client client.Client + rt http.RoundTripper + cache *ristretto.Cache +} + +func getCacheKey(userName, namespace string) (key uint64) { + hasher := _hashPool.Get().(hash.Hash64) + hasher.Reset() + hasher.Write([]byte(namespace)) + hasher.Write([]byte(userName)) + key = hasher.Sum64() + _hashPool.Put(hasher) + return +} + +func (rt *cachedRoundTripper) getToken(req *http.Request) (token string, err error) { + userName := req.Header.Get(utils.HeaderRafayUserName) + namespace := req.Header.Get(utils.HeaderRafayNamespace) + clearCache := req.Header.Get(utils.HeaderClearSecret) + if userName == "" || namespace == "" { + logger.Error( + nil, + "getToken failed", + "userName", userName, + "namespace", namespace, + "req", req, + ) + return "", ErrInvalidUser + } + + logger.Debug( + "getToken for ", + "user", userName, + "namespace", namespace, + "clearCache", clearCache, + ) + + key := getCacheKey(userName, namespace) + + if clearCache == "" { + if val, ok := rt.cache.Get(key); ok { + if strVal, ok := val.(string); ok { + logger.Debug( + "token found in cache ", + "user", userName, + "namespace", namespace, + ) + token = strVal + return + } + } + } else { + // Clear the header + req.Header.Del(utils.HeaderClearSecret) + } + + ctx, cancel := context.WithTimeout(req.Context(), time.Second*10) + defer cancel() + + secret, err := getServiceAccountSecret(ctx, rt.client, userName, namespace) + if err != nil { + return "", err + } + + token = string(secret.Data[tokenKey]) + rt.cache.SetWithTTL(key, token, 100, time.Minute*5) + return + +} + +func (rt *cachedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + logger.Debug("RoundTrip", "request", req) + // cache here + token, err := rt.getToken(req) + if err != nil { + logger.Error( + err, + "failed getToken in RoundTrip", + "req", req, + ) + return nil, err + } + + req = utilnet.CloneRequest(req) + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + // remove x-rafay heders before sending to kube-apiserver + req.Header.Del("X-Rafay-Key") + req.Header.Del("X-Rafay-Namespace") + req.Header.Del("X-Rafay-User") + + return rt.rt.RoundTrip(req) +} + +func (rt *cachedRoundTripper) CancelRequest(req *http.Request) { + tryCancelRequest(rt.WrappedRoundTripper(), req) +} + +func (rt *cachedRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt } + +func tryCancelRequest(rt http.RoundTripper, req *http.Request) { + type canceler interface { + CancelRequest(*http.Request) + } + switch rt := rt.(type) { + case canceler: + rt.CancelRequest(req) + case utilnet.RoundTripperWrapper: + tryCancelRequest(rt.WrappedRoundTripper(), req) + default: + logger.Error(nil, "Unable to cancel request") + } +} diff --git a/components/relay/pkg/proxy/service_account.go b/components/relay/pkg/proxy/service_account.go new file mode 100644 index 0000000..e1a7336 --- /dev/null +++ b/components/relay/pkg/proxy/service_account.go @@ -0,0 +1,157 @@ +package proxy + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/apply" + clientutil "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/client" + cruntime "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/runtime" + clusterv2 "github.com/RafaySystems/rcloud-base/components/common/proto/types/controller" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + corev1 "k8s.io/api/core/v1" + k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +var ( + svclogger = relaylogger.NewLogger(utils.LogLevel).WithName("ServiceAccount") + // ErrNotSAToken is returned when secret refered by service account is not of type service account token + ErrNotSAToken = errors.New("secert is not of type ServiceAccountToken") +) + +const ( + caCertKey = "ca.crt" + tokenKey = "token" +) + +// getServiceAccountSecret returns secret for the service account +func getServiceAccountSecret(ctx context.Context, c k8sclient.Client, name, namespace string) (*corev1.Secret, error) { + var serviceAccount corev1.ServiceAccount + var secret corev1.Secret + + err := c.Get(ctx, k8sclient.ObjectKey{ + Name: name, + Namespace: namespace, + }, &serviceAccount) + + if err != nil { + return nil, err + } + + for _, saSecret := range serviceAccount.Secrets { + err = c.Get(ctx, k8sclient.ObjectKey{ + Name: saSecret.Name, + Namespace: namespace, + }, &secret) + if err != nil { + continue + } + + if secret.Type == corev1.SecretTypeServiceAccountToken { + return &secret, nil + } + } + + return nil, fmt.Errorf("service account %s/%s does not have secrets of type ServiceAccountToken", namespace, name) +} + +//DeleteServiceAccount from cluster and cache +func DeleteServiceAccount(key, rafayAuthzSA, rafayAuthzRole, rafayAuthzRoleBind string, delCache bool) { + svclogger.Debug( + "DeleteServiceAccount", + key, + "not evicted", delCache, + ) + dstrSA, err1 := base64.StdEncoding.DecodeString(rafayAuthzSA) + dstrRole, err2 := base64.StdEncoding.DecodeString(rafayAuthzRole) + dstrRB, err3 := base64.StdEncoding.DecodeString(rafayAuthzRoleBind) + + if err1 == nil && err2 == nil && err3 == nil { + // delete sa,role,rolebinding + svclogger.Debug( + "delete service account", + "yaml SA:", + string(dstrSA), + "yaml Role:", + string(dstrRole), + "yaml RoleBind:", + string(dstrRB), + ) + + c, err := clientutil.New() + if err != nil { + svclogger.Error( + err, + "failed in clientutil new", + ) + return + } + applier := apply.NewApplier(c) + + sa, err1 := getObject(dstrSA) + role, err2 := getObject(dstrRole) + rb, err3 := getObject(dstrRB) + + if err1 == nil && err2 == nil && err3 == nil { + deleteAuthz(applier, rb) + deleteAuthz(applier, role) + deleteAuthz(applier, sa) + if delCache { + //delete from service cache + utils.DeleteCache(utils.ServiceAccountCache, key) + } + + return + } + + svclogger.Error( + nil, + "failed to cleanup service account", + "serviceaccount", string(dstrSA), + "role", string(dstrRole), + "rolebinding", string(dstrRB), + ) + } +} + +func getObject(yamlData []byte) (k8sclient.Object, error) { + jb, err := yaml.YAMLToJSON(yamlData) + if err != nil { + return nil, err + } + + var so clusterv2.StepObject + err = json.Unmarshal(jb, &so) + if err != nil { + return nil, err + } + + o, _, err := cruntime.ToUnstructuredObject(&so) + if err != nil { + return nil, err + } + + return o, nil +} + +func deleteAuthz(applier apply.Applier, obj k8sclient.Object) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + err := applier.Delete(ctx, obj) + if err != nil { + svclogger.Error( + err, + "failed to delete sa/role/rolebinding", + ) + return err + } + + return nil +} diff --git a/components/relay/pkg/proxy/tcpproxy.go b/components/relay/pkg/proxy/tcpproxy.go new file mode 100644 index 0000000..70de9ce --- /dev/null +++ b/components/relay/pkg/proxy/tcpproxy.go @@ -0,0 +1,244 @@ +package proxy + +import ( + "crypto/tls" + "io" + "net" + "net/http" + "net/url" + "os" + "syscall" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + k8proxy "k8s.io/apimachinery/pkg/util/proxy" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +// TCPProxy forwards TCP streams. +type TCPProxy struct { + // logger is the proxy logger. + logger *relaylogger.RelayLog + config *utils.ProxyConfig + handler *k8proxy.UpgradeAwareHandler + //kube-apiserver address + apiHost string +} + +func (tp *TCPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { + u, err := url.Parse(tp.apiHost) + if err == nil { + r.URL.Host = u.Host + r.URL.Scheme = u.Scheme + r.Host = u.Host + } + r.Header.Set("Host", tp.apiHost) + utils.UnSetXForwardedRafay(r.Header) + tp.handler.ServeHTTP(w, r) +} + +func (tp *TCPProxy) backendProxy() error { + socketPath := utils.UNIXAGENTSOCKET + tp.config.ServiceSNI + syscall.Unlink(socketPath) + + l, err := net.Listen("unix", socketPath) + if err != nil { + tp.logger.Error( + err, + "failed to listen on unix", + "socketPath", socketPath, + ) + return err + } + + tp.logger.Info( + "started listening on unix", + "socketPath", socketPath, + ) + + cfg, err := config.GetConfig() + if err != nil { + tp.logger.Error( + err, + "failed in NewKubeHandler", + ) + return nil + } + + hndlr, err := NewCachedKubeHandler(cfg, utils.DefaultKeepAliveInterval) + if err != nil { + tp.logger.Error( + err, + "unable to create kube handler for config", + ) + return err + } + + tp.handler = hndlr + + go func() { + + s := &http.Server{ + Handler: http.Handler(tp), + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + + if err := s.Serve(l); err != nil { + tp.logger.Error( + err, + "unable to start backend server", + ) + } + }() + + tp.handler = hndlr + + return nil +} + +// NewKubeCtlTCPProxy creates new direct TCPProxy, everything will be proxied to +// unix socket. +func NewKubeCtlTCPProxy(lg *relaylogger.RelayLog, cfg *utils.ProxyConfig) *TCPProxy { + tp := &TCPProxy{ + logger: lg, + config: cfg, + } + + err := tp.backendProxy() + if err != nil { + return nil + } + + host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") + if len(host) == 0 || len(port) == 0 { + ec, err := config.GetConfig() + if err != nil { + tp.logger.Error( + err, + "failed to get kubeapi host:port", + ) + return nil + } + tp.apiHost = ec.Host + } else { + tp.apiHost = "https://" + net.JoinHostPort(host, port) + } + + return tp +} + +// NewTCPProxy creates new direct TCPProxy, everything will be proxied to +// unix socket. +func NewTCPProxy(lg *relaylogger.RelayLog, cfg *utils.ProxyConfig) *TCPProxy { + tp := &TCPProxy{ + logger: lg, + config: cfg, + } + + host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") + if len(host) == 0 || len(port) == 0 { + ec, err := config.GetConfig() + if err != nil { + tp.logger.Error( + err, + "failed to get kubeapi host:port", + ) + return nil + } + tp.apiHost = ec.Host + } else { + tp.apiHost = "https://" + net.JoinHostPort(host, port) + } + + return tp +} + +// Proxy is a ProxyFunc. +func (tp *TCPProxy) Proxy(w io.Writer, r io.ReadCloser, msg *utils.ControlMessage, req *http.Request) { + var target string + var network string + var directionTO, directionFrom string + + switch tp.config.Protocol { + case utils.CDAGENTCORE: + if tp.config.Upstream != "" { + target = tp.config.Upstream + } else { + target = utils.DefaultTCPUpstream + } + network = "tcp" + directionTO = "tunnel to TCP" + directionFrom = "TCP to tunnel" + default: + target = utils.UNIXAGENTSOCKET + tp.config.ServiceSNI + network = "unix" + directionTO = "tunnel to unix" + directionFrom = "unix to tunnel" + } + + if target == "" { + tp.logger.Info( + "no unix target", + "ctrlMsg", msg, + ) + return + } + + local, err := net.DialTimeout(network, target, utils.DefaultMuxTimeout) + if err != nil { + tp.logger.Error( + err, + "msg dial failed", + "target", target, + "ctrlMsg", msg, + "err", err, + ) + return + } + defer local.Close() + + done := make(chan struct{}) + go func() { + utils.Transfer(flushWriter{w, tp.logger, local}, local, tp.logger, directionFrom) + close(done) + }() + + utils.Transfer(local, r, tp.logger, directionTO) + +proxyDone: + for { + select { + case <-req.Context().Done(): + break proxyDone + case <-done: + break proxyDone + } + } + // wait fr 2 sec before closing + time.Sleep(2 * time.Second) +} + +type flushWriter struct { + w io.Writer + lg *relaylogger.RelayLog + c net.Conn +} + +func (fw flushWriter) Write(p []byte) (n int, err error) { + n, err = fw.w.Write(p) + fw.lg.Debug( + "data wrote", + "data", string(p), + ) + if f, ok := fw.w.(http.Flusher); ok { + f.Flush() + } else { + fw.lg.Info( + "flushWriter http.Flusher not ok", + ) + } + // tolerate up to utils.IdleTimeout idletime + fw.c.SetDeadline(time.Now().Add(utils.IdleTimeout)) + return +} diff --git a/components/relay/pkg/proxy/testdata/role.yaml b/components/relay/pkg/proxy/testdata/role.yaml new file mode 100644 index 0000000..090e7f4 --- /dev/null +++ b/components/relay/pkg/proxy/testdata/role.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: rafay-core-debug + namespace: default + labels: + creator: rafay-relay + expire: "yes" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: rafay-core-debug-role + namespace: default +rules: +- apiGroups: + - "" + resources: + - "*" + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: rafay-core-debug-binding + namespace: default +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: rafay-core-debug-role +subjects: +- kind: ServiceAccount + name: rafay-core-debug diff --git a/components/relay/pkg/relay/relay.go b/components/relay/pkg/relay/relay.go new file mode 100644 index 0000000..d7c394e --- /dev/null +++ b/components/relay/pkg/relay/relay.go @@ -0,0 +1,888 @@ +package relay + +import ( + "context" + "crypto/x509/pkix" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/cryptoutil" + "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/register" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/tunnel" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/spf13/viper" +) + +const ( + podNameEnv = "POD_NAME" + podNamespaceEnv = "POD_NAMESPACE" + peerServiceURIEnv = "RAFAY_RELAY_PEERSERVICE" + relayListenIPEnv = "RELAY_LISTENIP" + sentryAddrEnv = "SENTRY_ADDR" // used by relay running inside core + auditPathEnv = "AUDIT_PATH" + metricListenEnv = "METRIC_LISTEN" + + // for relays running outside of core + // supply bootstrapAddr and token as environment variables + bootstrapAddrEnv = "BOOTSTRAP_ADDR" + relayPeeringTokenEnv = "RELAY_PEERING_TOKEN" + relayUserTokenEnv = "RELAY_USER_TOKEN" + relayConnectorTokenEnv = "RELAY_CONNECTOR_TOKEN" + relayUserHostPortEnv = "RELAY_USER_HOST_PORT" + relayConnectorHostPortEnv = "RELAY_CONNECTOR_HOST_PORT" + relayNetworkIDEnv = "RELAY_NETWORK_ID" + + // cd relay + cdRelayUserTokenEnv = "CD_RELAY_USER_TOKEN" + cdRelayConnectorTokenEnv = "CD_RELAY_CONNECTOR_TOKEN" +) + +var ( + podName string + podNamespace string + relayPeeringURI string + relayListenIP string + log *relaylogger.RelayLog + sentryAddr string // used by relay running inside core + auditPath string + metricListen string + + // for relays running outside of core + relayPeeringToken string + relayConnectorToken string + relayUserToken string + bootstrapAddr string + relayUserHostPort string + relayConnectorHostPort string + relayNetworkID string + + // cd relay + cdRelayConnectorToken string + cdRelayUserToken string +) + +func setupserver(log *relaylogger.RelayLog) error { + // Enable VIPER to read Environment Variables + viper.AutomaticEnv() + + viper.SetDefault(podNameEnv, "relay-pod") + viper.SetDefault(podNamespace, "rafay-system") + viper.SetDefault(peerServiceURIEnv, "https://peering.sentry.rafay.local:10001") + viper.SetDefault(sentryAddrEnv, "localhost:10000") + + // for relays running outside of core + viper.SetDefault(bootstrapAddrEnv, "") + viper.SetDefault(relayPeeringTokenEnv, "-") + viper.SetDefault(relayUserToken, "-") + viper.SetDefault(relayConnectorToken, "-") + viper.SetDefault(relayUserHostPortEnv, "") + viper.SetDefault(relayConnectorHostPortEnv, "") + viper.SetDefault(auditPathEnv, "/tmp") + viper.SetDefault(metricListenEnv, ":8003") + viper.SetDefault(relayNetworkIDEnv, "rafay-core-64a66h0asw") + + viper.BindEnv(podNameEnv) + viper.BindEnv(podNamespaceEnv) + viper.BindEnv(relayPeeringTokenEnv) + viper.BindEnv(sentryAddrEnv) + + // outside core configs + viper.BindEnv(bootstrapAddrEnv) + viper.BindEnv(relayUserHostPortEnv) + viper.BindEnv(relayConnectorHostPortEnv) + viper.BindEnv(relayUserTokenEnv) + viper.BindEnv(relayConnectorTokenEnv) + viper.BindEnv(relayNetworkIDEnv) + + // for testing + viper.BindEnv(relayListenIPEnv) + + podName = viper.GetString(podNameEnv) + podNamespace = viper.GetString(podNamespaceEnv) + sentryAddr = viper.GetString(sentryAddrEnv) + auditPath = viper.GetString(auditPathEnv) + + utils.PODNAME = podName + + relayPeeringURI = viper.GetString(peerServiceURIEnv) + + // outside core relay configurations + bootstrapAddr = viper.GetString(bootstrapAddrEnv) + relayUserHostPort = viper.GetString(relayUserHostPortEnv) + relayConnectorHostPort = viper.GetString(relayConnectorHostPortEnv) + relayNetworkID = viper.GetString(relayNetworkIDEnv) + + if sentryAddr == "" && bootstrapAddr == "" { + log.Error( + nil, + "missing sentry & bootstrap uri, please set one of the environment variable RAFAY_SENTRY[relay deployed inside core] (or) BOOTSTRAP_ADDR[outside core]", + ) + return fmt.Errorf("relay server failed in setupserver") + } + + if bootstrapAddr != "" { + // outside config found, unset inside boostrap endpoint. + // both cannot be set, only one mode is possible + sentryAddr = "" + } + + if relayPeeringURI == "" { + log.Error( + nil, + "missing relay peer service uri, please set RAFAY_RELAY_PEERSERVICE environment variable", + ) + return fmt.Errorf("relay server failed in setupserver") + } + + relayPeeringToken = viper.GetString(relayPeeringTokenEnv) + relayUserToken = viper.GetString(relayUserTokenEnv) + relayConnectorToken = viper.GetString(relayConnectorTokenEnv) + relayListenIP = viper.GetString(relayListenIPEnv) // used for running multiple relays in local test hosts + metricListen = viper.GetString(metricListenEnv) + + log.Info( + "relay server setup values", + "podName", podName, + "podNamespace", podNamespace, + "relayPeeringURI", relayPeeringURI, + "relayListenIP", relayListenIP, + ) + + if sentryAddr != "" { + log.Info("relay inside core: sentryAddr", sentryAddr) + } else { + log.Info( + "outside core relay: bootStrapURI", bootstrapAddr, + "relayPeeringToken", relayPeeringToken, + "relayUserToken", relayUserToken, + "relayConnectorToken", relayConnectorToken, + ) + if relayPeeringToken == "-" || relayUserToken == "-" || relayConnectorToken == "-" || relayUserHostPort == "" || relayConnectorHostPort == "" { + log.Error( + fmt.Errorf("missing env variable"), + "outside core relay: one of the required token is missing, please set missing environment variables", + "RELAY_PEERING_TOKEN", relayPeeringToken, + "RELAY_USER_TOKEN", relayUserToken, + "RELAY_CONNECTOR_TOKEN", relayConnectorToken, + "RELAY_USER_HOST_PORT", relayUserHostPort, + "RELAY_CONNECTOR_HOST_PORT", relayConnectorHostPort, + ) + return fmt.Errorf("relay server failed in setupserver") + } + + // process the user facing host:port + host, port, err := net.SplitHostPort(relayUserHostPort) + if err != nil { + log.Error( + err, + "failed to process RELAY_USER_HOST_PORT env variable", relayUserHostPort, + ) + return fmt.Errorf("relay server failed in setupserver") + } + utils.RelayUserPort, err = strconv.Atoi(port) + if err != nil { + log.Error( + err, + "invalid port in RELAY_USER_HOST_PORT env variable", relayUserHostPort, + ) + return fmt.Errorf("relay server failed in setupserver") + } + utils.RelayUserHost = host + + // process the connector facing host:port + host, port, err = net.SplitHostPort(relayConnectorHostPort) + if err != nil { + log.Error( + err, + "failed to process RELAY_CONNECTOR_HOST_PORT env variable", relayConnectorHostPort, + ) + return fmt.Errorf("relay server failed in setupserver") + } + utils.RelayConnectorPort, err = strconv.Atoi(port) + if err != nil { + log.Error( + err, + "invalid port in RELAY_CONNECTOR_HOST_PORT env variable", relayConnectorHostPort, + ) + return fmt.Errorf("relay server failed in setupserver") + } + utils.RelayConnectorHost = host + } + + utils.RelayIPFromConfig = relayListenIP + return nil +} + +func setupCDServer(log *relaylogger.RelayLog) error { + // Enable VIPER to read Environment Variables + viper.AutomaticEnv() + + viper.SetDefault(podNameEnv, "cdrelay-pod") + viper.SetDefault(podNamespace, "rafay-system") + viper.SetDefault(peerServiceURIEnv, "https://peering.sentry.rafay.local:10001") + viper.SetDefault(sentryAddrEnv, "localhost:10000") + + // for relays running outside of core + viper.SetDefault(bootstrapAddrEnv, "") + viper.SetDefault(relayPeeringTokenEnv, "-") + viper.SetDefault(cdRelayUserTokenEnv, "-") + viper.SetDefault(cdRelayConnectorTokenEnv, "-") + viper.SetDefault(auditPathEnv, "/tmp") + viper.SetDefault(metricListenEnv, ":8003") + + viper.BindEnv(podNameEnv) + viper.BindEnv(podNamespaceEnv) + viper.BindEnv(relayPeeringTokenEnv) + viper.BindEnv(sentryAddrEnv) + + // outside core configs + viper.BindEnv(bootstrapAddrEnv) + viper.BindEnv(cdRelayUserTokenEnv) + viper.BindEnv(cdRelayConnectorTokenEnv) + + // for testing + viper.BindEnv(relayListenIPEnv) + + podName = viper.GetString(podNameEnv) + podNamespace = viper.GetString(podNamespaceEnv) + sentryAddr = viper.GetString(sentryAddrEnv) + auditPath = viper.GetString(auditPathEnv) + + utils.PODNAME = podName + + relayPeeringURI = viper.GetString(peerServiceURIEnv) + + if sentryAddr == "" { + log.Error( + nil, + "missing sentry uri, please set one of the environment variable RAFAY_SENTRY[relay deployed inside core] (or) BOOTSTRAP_ADDR[outside core]", + ) + return fmt.Errorf("relay server failed in setupserver") + } + + if relayPeeringURI == "" { + log.Error( + nil, + "missing relay peer service uri, please set RAFAY_RELAY_PEERSERVICE environment variable", + ) + return fmt.Errorf("relay server failed in setupserver") + } + + relayPeeringToken = viper.GetString(relayPeeringTokenEnv) + cdRelayUserToken = viper.GetString(cdRelayUserTokenEnv) + cdRelayConnectorToken = viper.GetString(cdRelayConnectorTokenEnv) + relayListenIP = viper.GetString(relayListenIPEnv) // used for running multiple relays in local test hosts + metricListen = viper.GetString(metricListenEnv) + + log.Debug( + "relay server setup values", + "podName", podName, + "podNamespace", podNamespace, + "relayPeeringURI", relayPeeringURI, + "relayListenIP", relayListenIP, + ) + + utils.RelayIPFromConfig = relayListenIP + return nil +} + +// prepare config for outside relay boot strapping +func prepareConfigCSRForBootStrapOutSideCore(config *register.Config, CN string, log *relaylogger.RelayLog) error { + privKey, err := cryptoutil.GenerateECDSAPrivateKey() + if err != nil { + log.Error( + err, + "failed generate ecd private key with CN", CN, + ) + return err + } + + key, err := cryptoutil.EncodePrivateKey(privKey, cryptoutil.NoPassword) + if err != nil { + log.Error( + err, + "failed encode ecd private key with CN", CN, + ) + return err + } + + config.PrivateKey = key + + csr, err := cryptoutil.CreateCSR(pkix.Name{ + CommonName: CN, + Country: []string{"USA"}, + Organization: []string{"Rafay Systems Inc"}, + Province: []string{"California"}, + Locality: []string{"Sunnyvale"}, + OrganizationalUnit: []string{relayNetworkID}, + }, privKey) + if err != nil { + log.Error( + err, + "failed create CSR with CN", CN, + ) + return err + } + + config.CSR = csr + return nil +} + +// registerRelayPeerService will register with rafay-sentry-peering-client template token +// registration fetches client-certificate/root-ca to connect to sentry peer service +func registerRelayPeerService(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "peering-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "peering-" + podName, + Mode: "client", + } + + if bootstrapAddr != "" { + // outside core bootstrap + cfg.Addr = bootstrapAddr + if relayPeeringToken == "" { + return fmt.Errorf("empty peering token for bootstrap") + } + cfg.TemplateToken = relayPeeringToken + cfg.Scheme = "http" + // check port is 443 then set scheme as https + if utils.IsHTTPS(bootstrapAddr) { + cfg.Scheme = "https" + } + // this is a client certificate CN is same as ClientID + err := prepareConfigCSRForBootStrapOutSideCore(cfg, cfg.ClientID, log) + if err != nil { + return fmt.Errorf("failed in config csr for relay peering bootstrap") + } + } else { + cfg.TemplateToken = "template/-" + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-sentry-peering-client" + } + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register peering relay", + ) + return err + } + + log.Info( + "peer certificate:", + string(cfg.Certificate), + ) + + utils.PeerCertificate = cfg.Certificate + utils.PeerPrivateKey = cfg.PrivateKey + utils.PeerCACertificate = cfg.CACertificate + utils.PeerServiceURI = relayPeeringURI + + return nil +} + +// registerCDRelayPeerService will register with rafay-sentry-cd-peering-client template token +// registration fetches client-certificate/root-ca to connect to sentry peer service +func registerCDRelayPeerService(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "peering-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "peering-" + podName, + Mode: "client", + } + + if bootstrapAddr != "" { + // outside core bootstrap + cfg.Addr = bootstrapAddr + if relayPeeringToken == "" { + return fmt.Errorf("empty peering token for bootstrap") + } + cfg.TemplateToken = relayPeeringToken + cfg.Scheme = "http" + // check port is 443 then set scheme as https + if utils.IsHTTPS(bootstrapAddr) { + cfg.Scheme = "https" + } + // this is a client certificate CN is same as ClientID + err := prepareConfigCSRForBootStrapOutSideCore(cfg, cfg.ClientID, log) + if err != nil { + return fmt.Errorf("failed in config csr for relay peering bootstrap") + } + } else { + cfg.TemplateToken = "template/cd-relay" + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-sentry-cd-peering-client" + } + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register cd peering relay", + ) + return err + } + + log.Info( + "peer certificate:", + string(cfg.Certificate), + ) + + utils.PeerCertificate = cfg.Certificate + utils.PeerPrivateKey = cfg.PrivateKey + utils.PeerCACertificate = cfg.CACertificate + utils.PeerServiceURI = relayPeeringURI + + return nil +} + +// registerRelayUserServer will register with rafay-core-relay-user template token +// registration fetches client-certificate/root-ca to terminate user connections +// the same certificate will be used as the client cert for peer-upstreams +func registerRelayUserServer(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "user-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "user-" + podName, + Mode: "server", + } + + if bootstrapAddr != "" { + // outside core bootstrap + cfg.Addr = bootstrapAddr + if relayUserToken == "" { + return fmt.Errorf("empty user token for bootstrap") + } + cfg.TemplateToken = relayUserToken + cfg.Scheme = "http" + // check port is 443 then set scheme as https + if utils.IsHTTPS(bootstrapAddr) { + cfg.Scheme = "https" + } + // this is a server certificate CN is same as ServerHost + cfg.ServerHost = utils.RelayUserHost + cfg.ServerPort = utils.RelayUserPort + err := prepareConfigCSRForBootStrapOutSideCore(cfg, cfg.ServerHost, log) + if err != nil { + return fmt.Errorf("failed in config csr for relay user server bootstrap") + } + } else { + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-core-relay-user" + } + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register user relay", + ) + return err + } + + log.Info( + "user certificate:", + string(cfg.Certificate), + ) + + utils.RelayUserCert = cfg.Certificate + utils.RelayUserKey = cfg.PrivateKey + utils.RelayUserCACert = cfg.CACertificate + utils.RelayUserPort = cfg.ServerPort + utils.RelayUserHost = cfg.ServerHost + + return nil +} + +// registerCDRelayUserServer will register with rafay-core-cd-relay-user template token +// registration fetches client-certificate/root-ca to terminate user connections +// the same certificate will be used as the client cert for peer-upstreams +func registerCDRelayUserServer(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "cd-user-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "cd-user-" + podName, + Mode: "server", + } + + //inside core bootstrap + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-core-cd-relay-user" + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register user cd relay", + ) + return err + } + + log.Info( + "user certificate:", + string(cfg.Certificate), + ) + + utils.CDRelayUserCert = cfg.Certificate + utils.CDRelayUserKey = cfg.PrivateKey + utils.CDRelayUserCACert = cfg.CACertificate + utils.CDRelayUserPort = cfg.ServerPort + utils.CDRelayUserHost = cfg.ServerHost + + return nil +} + +// registerRelayConnectorServer will register with rafay-core-relay-connector template token +// registration fetches client-certificate/root-ca to terminate connector connections +// the same certificate will be used as the client cert for peer-upstreams +func registerRelayConnectorServer(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "relay-server-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "relay-server-" + podName, + Mode: "server", + } + + if bootstrapAddr != "" { + // outside core bootstrap + cfg.Addr = bootstrapAddr + if relayConnectorToken == "" { + return fmt.Errorf("empty user token for bootstrap") + } + cfg.TemplateToken = relayConnectorToken + cfg.Scheme = "http" + // check port is 443 then set scheme as https + if utils.IsHTTPS(bootstrapAddr) { + cfg.Scheme = "https" + } + // this is a server certificate CN is same as ServerHost + cfg.ServerHost = utils.RelayConnectorHost + cfg.ServerPort = utils.RelayConnectorPort + err := prepareConfigCSRForBootStrapOutSideCore(cfg, cfg.ServerHost, log) + if err != nil { + return fmt.Errorf("failed in config csr for relay connector server bootstrap") + } + } else { + //inside core bootstrap + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-core-relay-server" + } + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register connector relay", + ) + return err + } + + log.Info( + "connector certificate:", + string(cfg.Certificate), + ) + + utils.RelayConnectorCert = cfg.Certificate + utils.RelayConnectorKey = cfg.PrivateKey + utils.RelayConnectorCACert = cfg.CACertificate + utils.RelayConnectorHost = cfg.ServerHost + utils.RelayConnectorPort = cfg.ServerPort + + return nil +} + +// registerCDRelayConnectorServer will register with rafay-core-cd-relay-connector template token +// registration fetches client-certificate/root-ca to terminate connector connections +// the same certificate will be used as the client cert for peer-upstreams +func registerCDRelayConnectorServer(ctx context.Context, log *relaylogger.RelayLog) error { + cfg := ®ister.Config{ + ClientID: "cd-relay-server-" + podName, + ClientIP: utils.GetRelayIP(), + Name: "cd-relay-server-" + podName, + Mode: "server", + } + + //inside core bootstrap + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-core-cd-relay-server" + + if err := register.Register(ctx, cfg); err != nil { + log.Error( + err, + "failed to register connector cd relay", + ) + return err + } + + log.Info( + "connector certificate:", + string(cfg.Certificate), + ) + + utils.CDRelayConnectorCert = cfg.Certificate + utils.CDRelayConnectorKey = cfg.PrivateKey + utils.CDRelayConnectorCACert = cfg.CACertificate + utils.CDRelayConnectorHost = cfg.ServerHost + utils.CDRelayConnectorPort = cfg.ServerPort + + return nil +} + +func relayServerBootStrap(ctx context.Context, log *relaylogger.RelayLog) { + //peer service bootstrap + ticker := time.NewTicker(5 * time.Second) + for { + err := registerRelayPeerService(ctx, log) + if err != nil { + log.Error( + err, + "failed to register relay with peer-service-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() + + ticker = time.NewTicker(5 * time.Second) + // relay user server bootstrap + for { + err := registerRelayUserServer(ctx, log) + if err != nil { + log.Error( + err, + "failed to register relay with relay-user-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() + + ticker = time.NewTicker(5 * time.Second) + // relay connector server bootstrap + for { + err := registerRelayConnectorServer(ctx, log) + if err != nil { + log.Error( + err, + "failed to register relay with relay-connector-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() +} + +func cdRelayServerBootStrap(ctx context.Context, log *relaylogger.RelayLog) { + //peer service bootstrap + ticker := time.NewTicker(5 * time.Second) + for { + err := registerCDRelayPeerService(ctx, log) + if err != nil { + log.Error( + err, + "failed to register cd relay with peer-service-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() + + ticker = time.NewTicker(5 * time.Second) + // relay user server bootstrap + for { + err := registerCDRelayUserServer(ctx, log) + if err != nil { + log.Error( + err, + "failed to register relay with cd-relay-user-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() + + ticker = time.NewTicker(5 * time.Second) + // relay connector server bootstrap + for { + err := registerCDRelayConnectorServer(ctx, log) + if err != nil { + log.Error( + err, + "failed to register relay with cd-relay-connector-bootstrap service, will retry", + ) + select { + case <-ticker.C: + continue + case <-ctx.Done(): + ticker.Stop() + return + } + } + break + } + ticker.Stop() +} + +//RunRelayServer entry to the relay server +func RunRelayServer(ctx context.Context, logLvl int) { + rctx, cancel := context.WithCancel(ctx) + defer cancel() + + log = relaylogger.NewLogger(logLvl).WithName("Relay Server") + + if err := setupserver(log); err != nil { + //config is not available + log.Error( + err, + "relay server exiting", + ) + utils.ExitChan <- true + return + } + +restartServer: + relayServerBootStrap(rctx, log) + go tunnel.StartServer(rctx, log, fmt.Sprintf("%s/%s", auditPath, podName), utils.ExitChan) + go runMetricServer(rctx, log) + + for { + select { + case <-utils.ExitChan: + log.Info( + "Relay server stopped, restart in 2 secs", + ) + time.Sleep(2 * time.Second) + goto restartServer + case <-ctx.Done(): + log.Info( + "Relay server exiting", + ) + return + } + } +} + +//RunCDRelayServer entry to the relay server +func RunCDRelayServer(ctx context.Context, logLvl int) { + rctx, cancel := context.WithCancel(ctx) + defer cancel() + + log = relaylogger.NewLogger(logLvl).WithName("Relay Server") + + if err := setupCDServer(log); err != nil { + //config is not available + log.Error( + err, + "relay server exiting", + ) + utils.ExitChan <- true + return + } + +restartServer: + cdRelayServerBootStrap(rctx, log) + go tunnel.StartCDServer(rctx, log, fmt.Sprintf("%s/%s", auditPath, podName), utils.ExitChan) + go runMetricServer(rctx, log) + + for { + select { + case <-utils.ExitChan: + log.Info( + "Relay server stopped, restart in 2 secs", + ) + time.Sleep(2 * time.Second) + goto restartServer + case <-ctx.Done(): + log.Info( + "Relay server exiting", + ) + return + } + } +} + +func runMetricServer(ctx context.Context, log *relaylogger.RelayLog) { + + m := http.NewServeMux() + s := http.Server{Addr: metricListen, Handler: m} + m.HandleFunc("/dialins", dialinMetrics) + m.HandleFunc("/loglevel", setLoglevel) + m.HandleFunc("/health", healthCheck) + + go func() { + <-ctx.Done() + s.Shutdown(context.Background()) + }() + log.Info("Start Relay Metric Server at", metricListen) + err := s.ListenAndServe() + log.Info("Stopping Relay Metric Server at", metricListen, err) +} + +func dialinMetrics(w http.ResponseWriter, r *http.Request) { + tunnel.DialinMetric(w) +} + +func setLoglevel(w http.ResponseWriter, r *http.Request) { + keys, ok := r.URL.Query()["level"] + if !ok || len(keys[0]) < 1 { + fmt.Fprintf(w, "missing level query string") + return + } + + level := keys[0] + lvl, err := strconv.Atoi(level) + if err != nil { + fmt.Fprintf(w, "invalid level query string") + return + } + + relaylogger.SetRunTimeLogLevel(lvl) + + fmt.Fprintf(w, "Success: set loglevel to "+level) +} + +func healthCheck(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "up and feels good") +} diff --git a/components/relay/pkg/relaylogger/relaylogger.go b/components/relay/pkg/relaylogger/relaylogger.go new file mode 100644 index 0000000..5a82884 --- /dev/null +++ b/components/relay/pkg/relaylogger/relaylogger.go @@ -0,0 +1,145 @@ +package relaylogger + +import ( + "bytes" + "fmt" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/log" + "go.uber.org/zap" +) + +var ( + logtmp = log.GetLogger() + plain = logtmp.SugaredLogger.Desugar().WithOptions(zap.AddCallerSkip(2)) + rlog = plain.Sugar() + runLevel = 0 +) + +// RelayLog is a sample logr.Logger that logs to stderr. +// It's terribly inefficient, and is *only* a basic example. +type RelayLog struct { + level int + name string + keyValues map[string]interface{} +} + +func (l *RelayLog) relaylog(level int, msg string, kvs ...interface{}) { + var buf bytes.Buffer + + if level > runLevel { + return + } + + fmt.Fprintf(&buf, "%s::%s ", l.name, msg) + for k, v := range l.keyValues { + fmt.Fprintf(&buf, "%s: %+v ", k, v) + } + + if len(kvs)%2 == 0 { + for i := 0; i < len(kvs); i += 2 { + fmt.Fprintf(&buf, "%s: %+v ", kvs[i], kvs[i+1]) + + } + } else { + for i := 0; i < len(kvs); i++ { + fmt.Fprintf(&buf, "%+v ", kvs[i]) + } + } + + fmt.Fprintf(&buf, "\n") + switch level { + case 0: + rlog.Panicw(buf.String()) + case 1: + rlog.Errorw(buf.String()) + case 2: + rlog.Warnw(buf.String()) + case 3: + rlog.Infow(buf.String()) + case 4: + rlog.Debugw(buf.String()) + } + +} + +//Enabled log enabled +func (_ *RelayLog) Enabled() bool { + return true +} + +//Critical log critical +func (l *RelayLog) Critical(msg string, kvs ...interface{}) { + l.relaylog(0, msg, kvs...) +} + +//Error log error level +func (l *RelayLog) Error(err error, msg string, kvs ...interface{}) { + kvs = append(kvs, "error", err) + l.relaylog(1, msg, kvs...) +} + +//Warn log warning level +func (l *RelayLog) Warn(msg string, kvs ...interface{}) { + l.relaylog(2, msg, kvs...) +} + +//Info log info level +func (l *RelayLog) Info(msg string, kvs ...interface{}) { + l.relaylog(3, msg, kvs...) +} + +//Debug log debug level +func (l *RelayLog) Debug(msg string, kvs ...interface{}) { + l.relaylog(4, msg, kvs...) +} + +//V .. +func (l *RelayLog) V(_ int) *RelayLog { + return l +} + +//WithName set log name +func (l *RelayLog) WithName(name string) *RelayLog { + var objName string + if l.name == "" { + objName = name + } else { + objName = l.name + "." + name + } + + return &RelayLog{ + level: l.level, + name: objName, + keyValues: l.keyValues, + } +} + +//WithValues log key values +func (l *RelayLog) WithValues(kvs ...interface{}) *RelayLog { + newMap := make(map[string]interface{}, len(l.keyValues)+len(kvs)/2) + for k, v := range l.keyValues { + newMap[k] = v + } + for i := 0; i < len(kvs); i += 2 { + newMap[kvs[i].(string)] = kvs[i+1] + } + return &RelayLog{ + level: l.level, + name: l.name, + keyValues: newMap, + } +} + +//NewLogger create new logger +func NewLogger(level int) *RelayLog { + if runLevel < level { + runLevel = level + } + return &RelayLog{level: level} +} + +//SetRunTimeLogLevel set a run time log level +func SetRunTimeLogLevel(level int) { + fmt.Println("log level changed from ", runLevel, " to ", level) + runLevel = level +} diff --git a/components/relay/pkg/sessions/usersession.go b/components/relay/pkg/sessions/usersession.go new file mode 100644 index 0000000..cc6fc45 --- /dev/null +++ b/components/relay/pkg/sessions/usersession.go @@ -0,0 +1,161 @@ +package sessions + +import ( + "hash" + "net/http" + "sync" + "time" + + "github.com/dgraph-io/ristretto" + "github.com/julienschmidt/httprouter" + "github.com/twmb/murmur3" +) + +//UserSession session cache +type UserSession struct { + // Type of the server. Relay means user-facing + // Dialin means cluster-facing + Type string + + // ServerName of the server which accepted the connection + ServerName string + + // CertSNI derived from client certificate + CertSNI string + + // server block of this connection + //server *Server + + // DialinCachedKey already stiched to a dialin + DialinCachedKey string + + // ErrorFlag indicate the session got 401/502 + ErrorFlag bool + + // UserName used in login + UserName string + + // RoleName Role + RoleName string + + // IsReadrole read/write + IsReadrole bool + + // IsReadrole OrgAdmin + IsOrgAdmin bool + + // EnforceOrgAdminOnlySecret access + EnforceOrgAdminOnlySecret bool +} + +var ( + // userSessions cache + userSessions *ristretto.Cache + + // for URL matching + roleCheck *httprouter.Router + + // for URL matching + roleCheckSecret *httprouter.Router + + _hashPool = sync.Pool{ + New: func() interface{} { + // The Pool's New function should generally only return pointer + // types, since a pointer can be put into the return interface + // value without an allocation: + return murmur3.New64() + }, + } +) + +var _dummyHandler = func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {} + +//InitUserSessionCache init user cache +func InitUserSessionCache() error { + var err error + userSessions, err = ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 25, // Maximum cost of cache (32MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + + roleCheck = httprouter.New() + roleCheck.Handle("POST", "/api/:version/namespaces/:namespace/pods/:pod/exec", _dummyHandler) + roleCheck.Handle("POST", "/api/:version/namespaces/:namespace/secrets", _dummyHandler) + roleCheck.Handle("GET", "/api/:version/namespaces/:namespace/secrets", _dummyHandler) + roleCheck.Handle("GET", "/api/:version/secrets", _dummyHandler) + + roleCheckSecret = httprouter.New() + roleCheckSecret.Handle("POST", "/api/:version/namespaces/:namespace/secrets", _dummyHandler) + roleCheckSecret.Handle("GET", "/api/:version/namespaces/:namespace/secrets", _dummyHandler) + roleCheckSecret.Handle("GET", "/api/:version/secrets", _dummyHandler) + + return err +} + +// GetRoleCheck check the given method and URL matches +func GetRoleCheck(method, url string) bool { + h, _, _ := roleCheck.Lookup(method, url) + return h != nil +} + +// GetRoleCheck check the given method and URL matches +func GetSecretRoleCheck(method, url string) bool { + h, _, _ := roleCheckSecret.Lookup(method, url) + return h != nil +} + +//GetUserSession get the user session +func GetUserSession(skey string) (*UserSession, bool) { + hkey := getUserCacheKey(skey) + if val, ok := userSessions.Get(hkey); ok { + return val.(*UserSession), true + } + return nil, false +} + +//AddUserSession add user session +func AddUserSession(s *UserSession, skey string) { + hkey := getUserCacheKey(skey) + userSessions.SetWithTTL(hkey, s, 100, time.Minute*15) +} + +//DeleteUserSession add user session +func DeleteUserSession(skey string) { + hkey := getUserCacheKey(skey) + userSessions.Del(hkey) +} + +//UpdateUserSessionExpiry set a short TTL when error happens +func UpdateUserSessionExpiry(skey string, secs int) { + hkey := getUserCacheKey(skey) + if val, ok := userSessions.Get(hkey); ok { + s := val.(*UserSession) + if !s.ErrorFlag { + s.ErrorFlag = true + userSessions.Del(hkey) + userSessions.SetWithTTL(hkey, s, 100, time.Second*time.Duration(secs)) + } + } +} + +//SetSessionErrorFlag ser session errflg +func SetSessionErrorFlag(skey string) { + hkey := getUserCacheKey(skey) + if val, ok := userSessions.Get(hkey); ok { + s := val.(*UserSession) + if !s.ErrorFlag { + s.ErrorFlag = true + } + } +} + +//getUserCacheKey get cache key +func getUserCacheKey(skey string) (key uint64) { + hasher := _hashPool.Get().(hash.Hash64) + hasher.Reset() + hasher.Write([]byte(skey)) + key = hasher.Sum64() + _hashPool.Put(hasher) + return +} diff --git a/components/relay/pkg/tail/follower/follower.go b/components/relay/pkg/tail/follower/follower.go new file mode 100644 index 0000000..a03f033 --- /dev/null +++ b/components/relay/pkg/tail/follower/follower.go @@ -0,0 +1,322 @@ +package follower + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "sync" + "time" + + "github.com/fsnotify/fsnotify" +) + +const ( + bufSize = 4 * 1024 + peekSize = 1024 +) + +var ( + _ = fmt.Print +) + +type Line struct { + bytes []byte + discarded int +} + +func (l *Line) Bytes() []byte { + return l.bytes +} + +func (l *Line) String() string { + return string(l.bytes) +} + +func (l *Line) Discarded() int { + return l.discarded +} + +type Config struct { + Offset int64 + Whence int + Reopen bool +} + +type Follower struct { + once sync.Once + file *os.File + filename string + lines chan Line + err error + config Config + reader *bufio.Reader + watcher *fsnotify.Watcher + offset int64 + closeCh chan struct{} +} + +func New(filename string, config Config) (*Follower, error) { + t := &Follower{ + filename: filename, + lines: make(chan Line), + config: config, + closeCh: make(chan struct{}), + } + + err := t.reopen() + if err != nil { + return nil, err + } + + go t.once.Do(t.run) + + return t, nil +} + +func (t *Follower) Lines() chan Line { + return t.lines +} + +func (t *Follower) Err() error { + return t.err +} + +func (t *Follower) Close() { + t.closeCh <- struct{}{} +} + +func (t *Follower) run() { + t.close(t.follow()) +} + +func (t *Follower) follow() error { + _, err := t.file.Seek(t.config.Offset, t.config.Whence) + if err != nil { + return err + } + + var ( + eventChan = make(chan fsnotify.Event) + errChan = make(chan error, 1) + ) + + t.watcher, err = fsnotify.NewWatcher() + if err != nil { + return err + } + + defer t.watcher.Close() + go t.watchFileEvents(eventChan, errChan) + + t.watcher.Add(t.filename) + + for { + for { + // discard leading NUL bytes + var discarded int + + for { + b, _ := t.reader.Peek(peekSize) + i := bytes.LastIndexByte(b, '\x00') + + if i > 0 { + n, _ := t.reader.Discard(i + 1) + discarded += n + } + + if i+1 < peekSize { + break + } + } + + s, err := t.reader.ReadBytes('\n') + if err != nil && err != io.EOF { + return err + } + + // if we encounter EOF before a line delimiter, + // ReadBytes() will return the remaining bytes, + // so push them back onto the buffer, rewind + // our seek position, and wait for further file changes. + // we also have to save our dangling byte count in the event + // that we want to re-open the file and seek to the end + if err == io.EOF { + l := len(s) + + t.offset, err = t.file.Seek(-int64(l), io.SeekCurrent) + if err != nil { + return err + } + + t.reader.Reset(t.file) + break + } + + t.sendLine(s, discarded) + } + + // we're now at EOF, so wait for changes + select { + case evt := <-eventChan: + switch evt.Op { + + // as soon as something is written, go back and read until EOF. + case fsnotify.Chmod: + fallthrough + + case fsnotify.Write: + fi, err := t.file.Stat() + if err != nil { + if !os.IsNotExist(err) { + return err + } + + // it's possible that an unlink can cause fsnotify.Chmod, + // so attempt to rewatch if the file is missing + if err := t.rewatch(); err != nil { + return err + } + + continue + } + + // file was truncated, seek to the beginning + if t.offset > fi.Size() { + t.offset, err = t.file.Seek(0, io.SeekStart) + if err != nil { + return err + } + + t.reader.Reset(t.file) + } + + continue + + // if a file is removed or renamed + // and re-opening is desired, see if it appears + // again within a 1 second deadline. this should be enough time + // to see the file again for log rotation programs with this behavior + default: + if !t.config.Reopen { + return nil + } + + if err := t.rewatch(); err != nil { + return err + } + + continue + } + + // any errors that come from fsnotify + case err := <-errChan: + return err + + // a request to stop + case <-t.closeCh: + t.watcher.Remove(t.filename) + return nil + + // fall back to 10 second polling if we haven't received any fsevents + // stat the file, if it's still there, just continue and try to read bytes + // if not, go through our re-opening routine + case <-time.After(10 * time.Second): + fi1, err := t.file.Stat() + if err != nil && !os.IsNotExist(err) { + return err + } + + fi2, err := os.Stat(t.filename) + if err != nil && !os.IsNotExist(err) { + return err + } + + if os.SameFile(fi1, fi2) { + continue + } + + if err := t.rewatch(); err != nil { + return err + } + + continue + } + } + +} + +func (t *Follower) rewatch() error { + t.watcher.Remove(t.filename) + if err := t.reopen(); err != nil { + return err + } + + t.watcher.Add(t.filename) + return nil +} + +func (t *Follower) reopen() error { + if t.file != nil { + t.file.Close() + t.file = nil + } + + file, err := os.Open(t.filename) + if err != nil { + return err + } + + t.file = file + t.reader = bufio.NewReaderSize(t.file, bufSize) + + return nil +} + +func (t *Follower) close(err error) { + t.err = err + + if t.file != nil { + t.file.Close() + } + + close(t.lines) +} + +func (t *Follower) sendLine(l []byte, d int) { + t.lines <- Line{l[:len(l)-1], d} +} + +func (t *Follower) watchFileEvents(eventChan chan fsnotify.Event, errChan chan error) { + for { + select { + case evt, ok := <-t.watcher.Events: + if !ok { + return + } + + // debounce write events, but send all others + switch evt.Op { + case fsnotify.Write: + select { + case eventChan <- evt: + default: + } + + default: + select { + case eventChan <- evt: + case err := <-t.watcher.Errors: + errChan <- err + return + } + } + + // die on a file watching error + case err := <-t.watcher.Errors: + errChan <- err + return + } + } +} diff --git a/components/relay/pkg/tail/register.go b/components/relay/pkg/tail/register.go new file mode 100644 index 0000000..8fbd4a6 --- /dev/null +++ b/components/relay/pkg/tail/register.go @@ -0,0 +1,109 @@ +package tail + +import ( + "context" + "crypto/x509/pkix" + "fmt" + "net/url" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/cryptoutil" + "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/register" + sentryrpc "github.com/RafaySystems/rcloud-base/components/common/proto/rpc/sentry" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" +) + +// prepare config for outside relay boot strapping +func prepareConfigCSRForBootStrapOutSideCore(config *register.Config, CN string) error { + privKey, err := cryptoutil.GenerateECDSAPrivateKey() + if err != nil { + _log.Infow("failed generate private key", "CN", CN) + return err + } + + key, err := cryptoutil.EncodePrivateKey(privKey, cryptoutil.NoPassword) + if err != nil { + _log.Infow("failed encode ecd private key", "cn", CN, "error", err) + return err + } + + config.PrivateKey = key + + csr, err := cryptoutil.CreateCSR(pkix.Name{ + CommonName: CN, + Country: []string{"USA"}, + Organization: []string{"Rafay Systems Inc"}, + OrganizationalUnit: []string{config.Name}, + Province: []string{"California"}, + Locality: []string{"Sunnyvale"}, + }, privKey) + if err != nil { + _log.Infow("failed to create CSR", "CN", CN, "error", err) + return err + } + + config.CSR = csr + return nil +} + +// registerRelayPeerService will register with rafay-sentry-peering-client template token +// registration fetches client-certificate/root-ca to connect to sentry peer service +func registerRelayPeerService(ctx context.Context) (*register.Config, error) { + cfg := ®ister.Config{ + ClientID: "tail-" + podName, + Name: "tail-" + podName, + Mode: "client", + } + + if bootstrapAddr != "" { + // outside core bootstrap + cfg.Addr = bootstrapAddr + if relayPeeringToken == "" { + return nil, fmt.Errorf("empty peering token for bootstrap") + } + cfg.TemplateToken = relayPeeringToken + cfg.Scheme = "http" + // check port is 443 then set scheme as https + if utils.IsHTTPS(bootstrapAddr) { + cfg.Scheme = "https" + } + // this is a client certificate CN is same as ClientID + err := prepareConfigCSRForBootStrapOutSideCore(cfg, cfg.ClientID) + if err != nil { + return nil, fmt.Errorf("failed in config csr for relay peering bootstrap") + } + } else { + cfg.TemplateToken = "template/-" + cfg.Addr = sentryAddr + cfg.Scheme = "grpc" + cfg.TemplateName = "rafay-sentry-peering-client" + } + + if err := register.Register(ctx, cfg); err != nil { + _log.Infow("unable to register", "error", err) + return nil, err + } + + _log.Infow("successfully registered", "cert", string(cfg.Certificate)) + + return cfg, nil +} + +func newSentryAuthorizationPool(ctx context.Context) (sentryrpc.SentryAuthorizationPool, error) { + + cfg, err := registerRelayPeerService(ctx) + if err != nil { + return nil, err + } + + url, err := url.Parse(relayPeeringURI) + if err != nil { + return nil, err + } + + return sentryrpc.NewSentryAuthorizationPool( + sentryrpc.WithAddr(url.Host), + sentryrpc.WithClientCertPEM(cfg.Certificate), + sentryrpc.WithClientKeyPEM(cfg.PrivateKey), + sentryrpc.WithCaCertPEM(cfg.CACertificate), + ) +} diff --git a/components/relay/pkg/tail/run.go b/components/relay/pkg/tail/run.go new file mode 100644 index 0000000..aefc4f7 --- /dev/null +++ b/components/relay/pkg/tail/run.go @@ -0,0 +1,213 @@ +package tail + +import ( + "context" + "fmt" + "os" + "sync" + "time" + + "github.com/segmentio/encoding/json" + "github.com/spf13/viper" +) + +const ( + auditPathEnv = "AUDIT_PATH" + podNameEnv = "POD_NAME" + podNamespaceEnv = "POD_NAMESPACE" + peerServiceURIEnv = "RAFAY_RELAY_PEERSERVICE" + sentryAddrEnv = "SENTRY_ADDR" // used by relay running inside core + bootstrapAddrEnv = "BOOTSTRAP_ADDR" + relayPeeringTokenEnv = "RELAY_PEERING_TOKEN" +) + +var ( + podName string + podNamespace string + relayPeeringURI string + sentryAddr string // used by relay running inside core + auditPath string + + // for relays running outside of core + relayPeeringToken string + bootstrapAddr string +) + +func setupTail() error { + // Enable VIPER to read Environment Variables + viper.AutomaticEnv() + + viper.SetDefault(podNameEnv, "relay-pod") + viper.SetDefault(podNamespace, "rafay-system") + viper.SetDefault(peerServiceURIEnv, "https://peering.sentry.rafay.local:10001") + viper.SetDefault(sentryAddrEnv, "localhost:10000") + + // for relays running outside of core + viper.SetDefault(bootstrapAddrEnv, "") + viper.SetDefault(relayPeeringTokenEnv, "-") + viper.SetDefault(auditPathEnv, "/tmp") + + viper.BindEnv(podNameEnv) + viper.BindEnv(podNamespaceEnv) + viper.BindEnv(relayPeeringTokenEnv) + viper.BindEnv(sentryAddrEnv) + + // outside core configs + viper.BindEnv(bootstrapAddrEnv) + + podName = viper.GetString(podNameEnv) + podNamespace = viper.GetString(podNamespaceEnv) + sentryAddr = viper.GetString(sentryAddrEnv) + auditPath = viper.GetString(auditPathEnv) + + relayPeeringURI = viper.GetString(peerServiceURIEnv) + + // outside core relay configurations + bootstrapAddr = viper.GetString(bootstrapAddrEnv) + + if sentryAddr == "" && bootstrapAddr == "" { + _log.Infow("missing sentry & bootstrap uri, please set one of the environment variable RAFAY_SENTRY[relay deployed inside core] (or) BOOTSTRAP_ADDR[outside core]") + return fmt.Errorf("relay server failed in setupserver") + } + + if bootstrapAddr != "" { + // outside config found, unset inside boostrap endpoint. + // both cannot be set, only one mode is possible + sentryAddr = "" + } + + if relayPeeringURI == "" { + _log.Infow("missing relay peer service uri, please set RAFAY_RELAY_PEERSERVICE environment variable") + return fmt.Errorf("relay server failed in setupserver") + } + + relayPeeringToken = viper.GetString(relayPeeringTokenEnv) + + _log.Infow( + "relay server setup values", + "podName", podName, + "podNamespace", podNamespace, + "relayPeeringURI", relayPeeringURI, + "auditPath", auditPath, + ) + + if sentryAddr != "" { + _log.Infow("relay inside core", "sentryAddr", sentryAddr) + } else { + _log.Infow("outside core relay", "bootStrapURI", bootstrapAddr, + "relayPeeringToken", relayPeeringToken, + ) + if relayPeeringToken == "-" { + _log.Infow("missing env variable", "RELAY_PEERING_TOKEN", relayPeeringToken) + return fmt.Errorf("relay server failed in setupserver") + } + + } + return nil +} + +func runTail(ctx context.Context) { + sap, err := newSentryAuthorizationPool(ctx) + if err != nil { + _log.Panicw("unable to create sentry authorization pool", "error", err) + } + + //_log.Infow("created sentry authorization pool") + + transformer, err := NewTransformer(sap) + if err != nil { + _log.Panicw("unable to create transformer", "error", err) + } + + //_log.Infow("created audit transformer") + + logChan := make(chan LogMsg) + + go func() { + ticker := time.NewTicker(time.Second * 30) + defer ticker.Stop() + + watchedDir := make(map[string]context.CancelFunc) + var m sync.Mutex + + // find new dirs in audit path + for ; true; <-ticker.C { + //_log.Infow("ticker started") + // check if context is done + select { + case <-ctx.Done(): + _log.Infow("context done") + for _, c := range watchedDir { + c() + } + break + default: + } + + staleDirs, err := findStaleDirs(auditPath) + if err != nil { + _log.Infow("unable to find state directories", "error", err) + continue + } + + remStaleLoop: + for _, staleDir := range staleDirs { + err := os.RemoveAll(staleDir) + if err != nil { + _log.Infow("unable to remove stale dir", "error", err) + continue remStaleLoop + } + } + + dirs, err := findLogDirs(auditPath) + if err != nil { + _log.Infow("unable to find log dirs", "error", err) + continue + } + + //_log.Infow("found log dirs", "dirs", dirs) + + for _, dir := range dirs { + m.Lock() + + if _, ok := watchedDir[dir]; !ok { + wCtx, cancel := context.WithCancel(ctx) + watchedDir[dir] = cancel + + go tailDir(wCtx, dir, logChan) + } + m.Unlock() + } + } + + }() + +transformLoop: + for { + select { + case lm := <-logChan: + var am AuditMsg + err := transformer.Transform(&lm, &am) + if err != nil { + _log.Infow("unable to transform message", "error", err) + continue + } + + err = json.NewEncoder(os.Stdout).Encode(&am) + if err != nil { + _log.Infow("unable to encode audit msg", "error", err) + continue + } + + case <-ctx.Done(): + break transformLoop + } + } + +} + +// RunRelayTail runs relay tail +func RunRelayTail(ctx context.Context) { + setupTail() + runTail(ctx) +} diff --git a/components/relay/pkg/tail/tail.go b/components/relay/pkg/tail/tail.go new file mode 100644 index 0000000..3266ddb --- /dev/null +++ b/components/relay/pkg/tail/tail.go @@ -0,0 +1,241 @@ +package tail + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "golang.org/x/time/rate" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/log" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/tail/follower" +) + +const ( + filterName = "lines.cuckoo" +) + +var ( + _log = log.GetLogger() +) + +func isStaleDir(path string) bool { + autitLogPath := fmt.Sprintf("%s/audit.log", path) + info, err := os.Stat(autitLogPath) + if err != nil { + _log.Infow("unable to stat file", "path", autitLogPath, "error", err) + return false + } + + // delete directory that has no modified files in last 7 days. + if info.ModTime().Before(time.Now().Add(-time.Hour * 24 * 7)) { + _log.Infow("audit log not updated for 7 days", "path", autitLogPath) + return true + } + + return false +} + +func hasLogs(path string) bool { + + matches, _ := filepath.Glob(fmt.Sprintf("%s/audit*.log", path)) + + return len(matches) > 0 +} + +func findStaleDirs(root string) ([]string, error) { + if !strings.HasSuffix(root, "/") { + root = root + "/" + } + + var staleDirs []string + var nonStaleDirs []string + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + _log.Infow("prevent panic by handling failure in accessing the path", path, "error", err) + return err + } + if info.IsDir() && hasLogs(path) { + if isStaleDir(path) { + staleDirs = append(staleDirs, path) + } else { + nonStaleDirs = append(nonStaleDirs, path) + } + } + + return nil + }) + + if err != nil { + return nil, err + } + + if len(nonStaleDirs) <= 0 { + // looks like all are stale directories, there are no logs for + // 7 days hence can't identify which one to delete. + // skip stale directory cleanup for now. + return nil, fmt.Errorf("can not identify stale vs non-stale directory") + } + + return staleDirs, nil +} + +func findLogDirs(root string) ([]string, error) { + + if !strings.HasSuffix(root, "/") { + root = root + "/" + } + + var auditDirs []string + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + _log.Infow("prevent panic by handling failure in accessing the path", path, "error", err) + return err + } + if info.IsDir() && hasLogs(path) && !isStaleDir(path) { + auditDirs = append(auditDirs, path) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return auditDirs, nil +} + +func tailRotatedFile(ctx context.Context, filePath string, tailChan chan<- LogMsg) error { + f, err := os.Open(filePath) + if err != nil { + return err + } + defer f.Close() + + limiter := rate.NewLimiter(rate.Every(time.Minute/200), 200) + + scanner := bufio.NewScanner(f) + + for scanner.Scan() { + + err := limiter.Wait(ctx) + if err != nil { + return err + } + + var lm LogMsg + err = json.Unmarshal(scanner.Bytes(), &lm) + if err != nil { + continue + } + + tailChan <- lm + } + + if err := scanner.Err(); err != nil { + return err + } + + // remove the file after done reading + os.Remove(filePath) + + return nil +} + +func tailCurrentFile(ctx context.Context, filePath string, tailChan chan<- LogMsg) error { + + t, err := follower.New(filePath, follower.Config{ + Whence: io.SeekStart, + Offset: 0, + Reopen: true, + }) + + if err != nil { + return err + } + + limiter := rate.NewLimiter(rate.Every(time.Minute/200), 200) + + for line := range t.Lines() { + + err := limiter.Wait(ctx) + if err != nil { + return err + } + + var lm LogMsg + err = json.Unmarshal(line.Bytes(), &lm) + if err != nil { + continue + } + + tailChan <- lm + + } + + if t.Err() != nil { + return err + } + + return nil +} + +// tailDir tails audit log directory for a relay pod +func tailDir(ctx context.Context, path string, logChan chan<- LogMsg) error { + + // filter, err := getFilter(path) + // if err != nil { + // return err + // } + + // defer saveFilter(path, filter) + + files := func() []string { + matches, _ := filepath.Glob(fmt.Sprintf("%s/audit*.log", path)) + + sort.Strings(matches) + return matches + }() + + //_log.Infow("found files", "files", files) + + tailChan := make(chan LogMsg) + for _, file := range files { + if strings.HasSuffix(file, "audit.log") { + go tailCurrentFile(ctx, file, tailChan) + } else { + go tailRotatedFile(ctx, file, tailChan) + } + } + +tailLoop: + for { + select { + case <-ctx.Done(): + break tailLoop + case log := <-tailChan: + + // // if the log xid is seen already ignore + // if filter.Lookup(lm.XID.Bytes()) { + // filter.Delete(lm.XID.Bytes()) + // continue + // } + + // filter.InsertUnique(lm.XID.Bytes()) + logChan <- log + + } + } + + return nil +} diff --git a/components/relay/pkg/tail/transform.go b/components/relay/pkg/tail/transform.go new file mode 100644 index 0000000..85db113 --- /dev/null +++ b/components/relay/pkg/tail/transform.go @@ -0,0 +1,217 @@ +package tail + +import ( + "bytes" + "context" + "fmt" + "hash" + "net/http" + "strings" + "sync" + "time" + + "github.com/dgraph-io/ristretto" + "github.com/jinzhu/inflection" + "github.com/twmb/murmur3" + + sentryrpc "github.com/RafaySystems/rcloud-base/components/common/proto/rpc/sentry" + "github.com/julienschmidt/httprouter" +) + +var _dummyHandler = func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {} + +var _bufPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +var _hashPool = sync.Pool{ + New: func() interface{} { + return murmur3.New64() + }, +} + +func getCacheKey(s string) uint64 { + hasher := _hashPool.Get().(hash.Hash64) + defer _hashPool.Put(hasher) + + buf := _bufPool.Get().(*bytes.Buffer) + defer _bufPool.Put(buf) + + buf.Reset() + buf.WriteString(s) + + hasher.Reset() + hasher.Write(buf.Bytes()) + return hasher.Sum64() +} + +// Transformer is the interface for transforming relay log message into audit message +type Transformer interface { + Transform(lm *LogMsg, am *AuditMsg) error +} + +// NewTransformer returns new relay log transformer +func NewTransformer(authzPool sentryrpc.SentryAuthorizationPool) (Transformer, error) { + + r := httprouter.New() + + r.Handle("GET", "/api", _dummyHandler) + r.Handle("GET", "/api/:version", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace/:kind1", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace/:kind1/:name", _dummyHandler) + r.Handle("GET", "/apis", _dummyHandler) + r.Handle("GET", "/apis/:group/:version", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace/:kind1", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace/:kind1/:name", _dummyHandler) + + cache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 27, // Maximum cost of cache (128MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + if err != nil { + return nil, err + } + + t := &transformer{ + authzPool: authzPool, + r: r, + cache: cache, + } + return t, nil +} + +type transformer struct { + authzPool sentryrpc.SentryAuthorizationPool + cache *ristretto.Cache + r *httprouter.Router +} + +func (t *transformer) getUser(cn string) (*sentryrpc.LookupUserResponse, error) { + key := getCacheKey(cn) + if val, ok := t.cache.Get(key); ok { + if usr, ok := val.(*sentryrpc.LookupUserResponse); ok { + return usr, nil + } + } + + // cache miss + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + authzClient, err := t.authzPool.NewClient(ctx) + if err != nil { + return nil, err + } + defer authzClient.Close() + + resp, err := authzClient.LookupUser(ctx, &sentryrpc.LookupUserRequest{ + UserCN: cn, + }) + + if err != nil { + return nil, err + } + + t.cache.Set(key, resp, 100) + return resp, nil +} + +func (t *transformer) getCluster(serverName string) (*sentryrpc.LookupClusterResponse, error) { + idx := strings.Index(serverName, ".") + + if idx > 0 { + key := getCacheKey(serverName[0:idx]) + if val, ok := t.cache.Get(key); ok { + if cluster, ok := val.(*sentryrpc.LookupClusterResponse); ok { + return cluster, nil + } + } + + // cache miss + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + authzClient, err := t.authzPool.NewClient(ctx) + if err != nil { + return nil, err + } + defer authzClient.Close() + + resp, err := authzClient.LookupCluster(ctx, &sentryrpc.LookupClusterRequest{ + ClusterSNI: serverName, + }) + + if err != nil { + return nil, err + } + + t.cache.Set(key, resp, 110) + return resp, nil + } + + return nil, fmt.Errorf("invalid serverName %s", serverName) +} + +func (t *transformer) getParams(url string) httprouter.Params { + + if h, params, _ := t.r.Lookup("GET", url); h != nil { + + return params + } + + return nil +} + +func (t *transformer) Transform(lm *LogMsg, am *AuditMsg) error { + + //_log.Infow("transforming", "logMessage", *lm) + cluster, err := t.getCluster(lm.ServerName) + if err != nil { + _log.Infow("unable to lookup cluster", "error", err) + return err + } + + user, err := t.getUser(lm.User) + if err != nil { + _log.Infow("unable to lookup user", "error", err) + return err + } + + params := t.getParams(lm.URL) + + am.ClusterName = cluster.Name + am.OrganizationID = user.OrganizationID + am.PartnerID = user.PartnerID + am.UserName = user.UserName + am.Duration = lm.Duration + am.Written = lm.Written + am.Timestamp = lm.Timestamp + am.Method = lm.Method + am.URL = lm.URL + am.Query = lm.Query + am.XID = lm.XID + am.StatusCode = lm.StatusCode + am.RemoteAddr = lm.RemoteAddr + am.SessionType = user.SessionType + + if params != nil { + am.APIVersion = fmt.Sprintf("%s/%s", params.ByName("group"), params.ByName("version")) + if params.ByName("kind1") != "" { + am.Kind = params.ByName("kind1") + } else { + am.Kind = params.ByName("kind0") + } + am.Kind = inflection.Singular(am.Kind) + am.Namespace = params.ByName("namespace") + am.Name = params.ByName("name") + } + + return nil +} diff --git a/components/relay/pkg/tail/types.go b/components/relay/pkg/tail/types.go new file mode 100644 index 0000000..6dff2d2 --- /dev/null +++ b/components/relay/pkg/tail/types.go @@ -0,0 +1,80 @@ +package tail + +import ( + "github.com/rs/xid" +) + +// { +// "level": "info", +// "ts": 1591154299.638035, +// "msg": "audit", +// "xid": "brbhcuug10lf2qfjj1hg", +// "method": "GET", +// "url": "/apis/metrics.k8s.io/v1beta1?timeout=32s", +// "serverName": "x3mxvkr.user.relay.rafay.local", +// "user": "rafay-core-debug", +// "remoteAddr": "127.0.0.1:63513", +// "statusCode": 503, +// "written": 20, +// "duration": 0.002513407 +// } + +// LogMsg represents log message +type LogMsg struct { + //Level string `json:"level"` + //Message string `json:"msg"` + Timestamp string `json:"ts"` + XID xid.ID `json:"xid"` + Method string `json:"method"` + URL string `json:"url"` + Query string `json:"query"` + ServerName string `json:"serverName"` + User string `json:"user"` + RemoteAddr string `json:"remoteAddr"` + StatusCode int `json:"statusCode"` + Written int64 `json:"written"` + Duration float64 `json:"duration"` +} + +// AuditMsg represents audit message +type AuditMsg struct { + Timestamp string `json:"ts"` + XID xid.ID `json:"id"` + StatusCode int `json:"sc"` + UserName string `json:"un"` + OrganizationID string `json:"o"` + PartnerID string `json:"p"` + RemoteAddr string `json:"ra"` + Duration float64 `json:"d"` + ClusterName string `json:"cn"` + APIVersion string `json:"av"` + Kind string `json:"k"` + Namespace string `json:"ns"` + Name string `json:"n"` + Method string `json:"m"` + URL string `json:"url"` + Query string `json:"q"` + Written int64 `json:"w"` + SessionType string `json:"st"` +} + +// Reset resets audit message +func (m *AuditMsg) Reset() { + m.Timestamp = "" + m.XID = xid.NilID() + m.StatusCode = 0 + m.UserName = "" + m.OrganizationID = "" + m.PartnerID = "" + m.RemoteAddr = "" + m.Duration = 0 + m.ClusterName = "" + m.APIVersion = "" + m.Kind = "" + m.Namespace = "" + m.Name = "" + m.Method = "" + m.URL = "" + m.Query = "" + m.Written = 0 +} diff --git a/components/relay/pkg/tail/url_test.go b/components/relay/pkg/tail/url_test.go new file mode 100644 index 0000000..c3a1602 --- /dev/null +++ b/components/relay/pkg/tail/url_test.go @@ -0,0 +1,76 @@ +package tail + +import ( + "testing" + + "github.com/julienschmidt/httprouter" +) + +// CASE 1 /api +// CASE 2 /apis +// CASE 3 /apis/storage.k8s.io/v1beta1 +// CASE 4 /api/v1/namespaces +// CASE 5 /api/v1/pods +// CASE 6 /api/v1/namespaces/rafay-system +// CASE 7 /api/v1/namespaces/rafay-system/configmaps/relay-agent-config +// CASE 8 /apis/k3s.cattle.io/v1/namespaces/kube-system/addons + +func TestURL(t *testing.T) { + r := httprouter.New() + + r.Handle("GET", "/api", _dummyHandler) + r.Handle("GET", "/api/:version", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace/:kind1", _dummyHandler) + r.Handle("GET", "/api/:version/:kind0/:namespace/:kind1/:name", _dummyHandler) + r.Handle("GET", "/apis", _dummyHandler) + r.Handle("GET", "/apis/:group/:version", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace/:kind1", _dummyHandler) + r.Handle("GET", "/apis/:group/:version/:kind0/:namespace/:kind1/:name", _dummyHandler) + + if h, params, _ := r.Lookup("GET", "/api/v1/pods"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + + if h, params, _ := r.Lookup("GET", "/apis/storage.k8s.io/v1beta1"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + + if h, params, _ := r.Lookup("GET", "/api/v1/namespaces/rafay-system/configmaps/relay-agent-config"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + + if h, params, _ := r.Lookup("GET", "/apis/k3s.cattle.io/v1/namespaces/kube-system/addons"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + + if h, params, _ := r.Lookup("GET", "/apis/metrics.k8s.io/v1beta1"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + + if h, params, _ := r.Lookup("GET", "/api/v1/namespaces/test-ns"); h != nil { + t.Log(params) + } else { + t.Error("expected match") + return + } + +} diff --git a/components/relay/pkg/tunnel/authz.go b/components/relay/pkg/tunnel/authz.go new file mode 100644 index 0000000..95b9640 --- /dev/null +++ b/components/relay/pkg/tunnel/authz.go @@ -0,0 +1,397 @@ +package tunnel + +import ( + "context" + "fmt" + "hash" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/log" + + "github.com/RafaySystems/rcloud-base/components/common/pkg/controller/apply" + "github.com/RafaySystems/rcloud-base/components/common/pkg/grpc" + sentryrpc "github.com/RafaySystems/rcloud-base/components/common/proto/rpc/sentry" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/proxy" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/dgraph-io/ristretto" + "github.com/twmb/murmur3" + "google.golang.org/grpc/credentials" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/transport" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +var ( + _log = log.GetLogger() +) + +const ( + bypassNamespace = "rafay-system" +) + +var bypassUserNames = []string{"system-sa", "default"} + +func getClusterID(sni string) (string, error) { + idx := strings.Index(sni, ".") + if idx < 0 { + return "", fmt.Errorf("invalid user sni format %s", sni) + } + return sni[0:idx], nil +} + +type bypassWrapper struct { + sni, key, bypassUserName string + rt http.RoundTripper +} + +func (w *bypassWrapper) RoundTrip(r *http.Request) (*http.Response, error) { + r.Header.Set("X-Rafay-User", w.bypassUserName) + r.Header.Set("X-Rafay-Key", w.key) + r.Header.Set("X-Rafay-Namespace", bypassNamespace) + + if !strings.HasSuffix(r.URL.Path, "/") { + r.URL.Path = r.URL.Path + "/" + } + + return w.rt.RoundTrip(r) +} + +func newBypassWrapper(sni, key, bypassUserName string) transport.WrapperFunc { + return func(rt http.RoundTripper) http.RoundTripper { + return &bypassWrapper{sni: sni, key: key, rt: rt, bypassUserName: bypassUserName} + } +} + +func getKubeRESTConfig(socketPath, sni, dialinLookupKey, username string) *rest.Config { + + cfg := &rest.Config{ + Host: fmt.Sprintf("%s_%s", sni, dialinLookupKey), + // Set transport expicitly to prevent client for caching transport + Transport: &http.Transport{ + DialContext: proxy.UnixDialContext(socketPath, dialinLookupKey, username, sni), + }, + Timeout: time.Second * 5, + } + + cfg.Wrap(newBypassWrapper(sni, dialinLookupKey, username)) + + return cfg + +} + +var _hashPool = sync.Pool{ + New: func() interface{} { + // The Pool's New function should generally only return pointer + // types, since a pointer can be put into the return interface + // value without an allocation: + return murmur3.New64() + }, +} + +type authzProvisioner struct { + clientCache *ristretto.Cache + authzCache *ristretto.Cache +} + +func newAuthzProvisioner() (*authzProvisioner, error) { + clientCache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 27, // Maximum cost of cache (256MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + if err != nil { + return nil, err + } + + authzCache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e4, // Num keys to track frequency of (10k). + MaxCost: 1 << 25, // Maximum cost of cache (32MB). + BufferItems: 64, // Number of keys per Get buffer. + }) + if err != nil { + return nil, err + } + + return &authzProvisioner{clientCache: clientCache, authzCache: authzCache}, nil +} + +func (p *authzProvisioner) getClient(socketPath, sni, dialinLookupKey string) (client.Client, error) { + + key := func() uint64 { + hasher := _hashPool.Get().(hash.Hash64) + hasher.Reset() + hasher.Write([]byte(sni)) + hasher.Write([]byte(dialinLookupKey)) + k := hasher.Sum64() + _hashPool.Put(hasher) + return k + }() + + if i, ok := p.clientCache.Get(key); ok { + if c, ok := i.(client.Client); ok { + return c, nil + } + } + + getK8sClient := func(bypassUserName string) (client.Client, error) { + cfg := getKubeRESTConfig(socketPath, sni, dialinLookupKey, bypassUserName) + mapper, err := apiutil.NewDynamicRESTMapper(cfg) + if err != nil { + return nil, err + } + + c, err := client.New(cfg, client.Options{Scheme: scheme.Scheme, Mapper: mapper}) + return c, err + } + + var c client.Client + var err error + for _, username := range bypassUserNames { + c, err = getK8sClient(username) + if err != nil { + _log.Infow("error getting k8s client for username", "username", username) + } else { + break + } + } + if err != nil { + return nil, err + } + p.clientCache.SetWithTTL(key, c, 100, time.Minute*30) + return c, nil +} + +func (p *authzProvisioner) getAuthz(ctx context.Context, userCN, sni string, certIssue int64) (*sentryrpc.GetUserAuthorizationResponse, error) { + clusterID, err := getClusterID(sni) + if err != nil { + _log.Infow("unable to get clusterID", "error", err) + return nil, err + } + + u, err := url.Parse(utils.PeerServiceURI) + if err != nil { + _log.Infow("unable to parse peer service url", "error", err) + return nil, err + } + //Load certificates + tlsConfig, err := ClientTLSConfigFromBytes(utils.PeerCertificate, utils.PeerPrivateKey, utils.PeerCACertificate, u.Host) + if err != nil { + _log.Infow("unable to build tls config for peer service", "error", err) + return nil, err + } + transportCreds := credentials.NewTLS(tlsConfig) + peerSeviceHost := u.Host + + conn, err := grpc.NewSecureClientConn(ctx, peerSeviceHost, transportCreds) + if err != nil { + _log.Infow("unable to connect to sentry", "error", err) + return nil, err + } + defer conn.Close() + + client := sentryrpc.NewClusterAuthorizationClient(conn) + + resp, err := client.GetUserAuthorization(ctx, &sentryrpc.GetUserAuthorizationRequest{ + UserCN: userCN, + ClusterID: clusterID, + CertIssueSeconds: certIssue, + }) + + if err != nil { + _log.Infow("unable to get authorization for user", "error", err) + return nil, err + } + return resp, nil +} + +func (p *authzProvisioner) provisionAuthzHandleRoleChange(auth *sentryrpc.GetUserAuthorizationResponse, socketPath, sni, dialinLookupKey string) { + k8sDelClient, err := p.getClient(socketPath, sni, dialinLookupKey) + if err != nil { + _log.Infow("unable to get del client", "sni", sni, "dialinLookupKey", dialinLookupKey, "error", err) + return + } + nctx, cancel := context.WithTimeout(context.Background(), time.Second*180) + defer cancel() + // Delete cluster role binding to clean if exist to handle change in user role + for _, crb := range auth.DeleteClusterRoleBindings { + //crbo, _, err := runtime.ToObject(crb) + if err == nil { + err := k8sDelClient.Delete(nctx, crb) + if err != nil { + _log.Debugw("unable to delete ClusterRoleBindings", "name", crb.TypeMeta.GetObjectKind(), "obj", crb, "err", err) + } else { + _log.Infow("successfully to deleted ClusterRoleBindings", "obj", crb) + } + } + } + + // To handle change in namespace changes delete rolebindings to clean if exist. + for _, rb := range auth.DeleteRoleBindings { + //rbo, _, err := runtime.ToObject(rb) + if err == nil { + err := k8sDelClient.Delete(nctx, rb) + if err != nil { + _log.Debugw("unable to delete RoleBindings", "name", rb.TypeMeta.GetObjectKind(), "obj", rb, "err", err) + } else { + _log.Infow("successfully to deleted RoleBindings", "obj", rb) + } + } + } +} + +func (p *authzProvisioner) provisionAuthz(ctx context.Context, auth *sentryrpc.GetUserAuthorizationResponse, socketPath, sni, dialinLookupKey string) error { + + k8sClient, err := p.getClient(socketPath, sni, dialinLookupKey) + if err != nil { + _log.Infow("unable to get client", "sni", sni, "dialinLookupKey", dialinLookupKey, "error", err) + return err + } + + // Handle deletion of excluded clusterrolebindings and rolebindings + // in the background. + go p.provisionAuthzHandleRoleChange(auth, socketPath, sni, dialinLookupKey) + if len(auth.ClusterRoles) > 0 { + _log.Infow("cluster scope authz for", "user", auth.ServiceAccount, "ClusterRoles", auth.ClusterRoles, "ClusterRoleBindings", auth.ClusterRoleBindings) + } + if len(auth.Roles) > 0 { + _log.Infow("namespace scope authz for", "user", auth.ServiceAccount, "Roles", auth.Roles, "RoleBindings", auth.RoleBindings, "Namespaces", auth.Namespaces) + } + + applier := apply.NewApplier(k8sClient) + + /*sao, _, err := runtime.ToObject(auth.ServiceAccount) + if err != nil { + _log.Infow("unable to make service account runtime object", "error", err) + return err + }*/ + + err = applier.Apply(ctx, auth.ServiceAccount) + // Ignore already exist error caused due to + // multiple inflight request trying to provision + // ZTKA JIT service account + if err != nil && !strings.Contains(err.Error(), "already exists") { + _log.Infow("unable to apply service account", "error", err) + return err + } + + for _, clusterRole := range auth.ClusterRoles { + /*cro, _, err := runtime.ToObject(clusterRole) + if err != nil { + _log.Infow("unable to make clusterRole runtime object", "error", err) + return err + }*/ + + err = applier.Apply(ctx, clusterRole) + if err != nil && !strings.Contains(err.Error(), "already exists") { + _log.Infow("unable to apply clusterRole", "error", err) + return err + } + } + + for _, clusterRoleBinding := range auth.ClusterRoleBindings { + /*crbo, _, err := runtime.ToObject(clusterRoleBinding) + if err != nil { + _log.Infow("unable to make clusterRoleBinding runtime object", "error", err) + return err + }*/ + + err = applier.Apply(ctx, clusterRoleBinding) + if err != nil && !strings.Contains(err.Error(), "already exists") { + _log.Infow("unable to apply clusterRoleBinding", "error", err) + return err + } + } + + for _, role := range auth.Roles { + /*cro, _, err := runtime.ToObject(role) + if err != nil { + _log.Infow("unable to make roles runtime object", "error", err) + return err + }*/ + + err = applier.Apply(ctx, role) + if err != nil && !strings.Contains(err.Error(), "already exists") { + _log.Infow("unable to apply Role", "error", err) + //Ignore any error + } + } + + for _, roleBinding := range auth.RoleBindings { + /*crbo, _, err := runtime.ToObject(roleBinding) + if err != nil { + _log.Infow("unable to make RoleBinding runtime object", "error", err) + return err + }*/ + + err = applier.Apply(ctx, roleBinding) + if err != nil && !strings.Contains(err.Error(), "already exists") { + _log.Infow("unable to apply RoleBinding", "error", err) + //Ignore any error + } + } + + _log.Infow("applied authz", "sni", sni, "user", auth.UserName) + + return nil +} + +type provisionedUser struct { + UserName string + Role string + IsReadRole bool + IsOrgAdmin bool + EnforceOrgAdminOnlySecretAccess bool +} + +func (p *authzProvisioner) ProvisionAuthzForUser(socketPath, userCN, sni, dialinLookupKey string, forceProvision, refreshSession bool, certIssue int64) (string, string, bool, bool, bool, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + key := func() uint64 { + hasher := _hashPool.Get().(hash.Hash64) + hasher.Reset() + hasher.Write([]byte(userCN)) + hasher.Write([]byte(sni)) + k := hasher.Sum64() + _hashPool.Put(hasher) + return k + }() + + if !forceProvision { + if i, ok := p.authzCache.Get(key); ok { + if pu, ok := i.(*provisionedUser); ok { + return pu.UserName, pu.Role, pu.IsReadRole, pu.IsOrgAdmin, pu.EnforceOrgAdminOnlySecretAccess, nil + } + } + } + + resp, err := p.getAuthz(ctx, userCN, sni, certIssue) + if err != nil { + return "", "", false, false, false, err + } + + applyctx, applycancel := context.WithTimeout(context.Background(), time.Second*60) + defer applycancel() + err = p.provisionAuthz(applyctx, resp, socketPath, sni, dialinLookupKey) + // ignore cluster provision failures during refresh. + // continue with existing RBAC. + if !refreshSession && err != nil { + return "", "", false, false, false, err + } + + p.authzCache.SetWithTTL(key, &provisionedUser{ + UserName: resp.UserName, + Role: resp.RoleName, + IsReadRole: resp.IsRead, + IsOrgAdmin: resp.IsOrgAdmin, + EnforceOrgAdminOnlySecretAccess: resp.EnforceOrgAdminOnlySecretAccess, + }, 100, time.Minute*5) + + return resp.UserName, resp.RoleName, resp.IsRead, resp.IsOrgAdmin, resp.EnforceOrgAdminOnlySecretAccess, nil +} diff --git a/components/relay/pkg/tunnel/backoff.go b/components/relay/pkg/tunnel/backoff.go new file mode 100644 index 0000000..d4fcccb --- /dev/null +++ b/components/relay/pkg/tunnel/backoff.go @@ -0,0 +1,15 @@ +package tunnel + +import "time" + +// Backoff defines behavior of staggering reconnection retries. +type Backoff interface { + // Next returns the duration to sleep before retrying to reconnect. + // If the returned value is negative, the retry is aborted. + NextBackOff() time.Duration + + // Reset is used to signal a reconnection was successful and next + // call to Next should return desired time duration for 1st reconnection + // attempt. + Reset() +} diff --git a/components/relay/pkg/tunnel/client.go b/components/relay/pkg/tunnel/client.go new file mode 100644 index 0000000..522e45e --- /dev/null +++ b/components/relay/pkg/tunnel/client.go @@ -0,0 +1,651 @@ +package tunnel + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/proxy" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/cenkalti/backoff" + "golang.org/x/net/http2" +) + +//ClientConfig .. +type ClientConfig struct { + //ServiceName name of the service + ServiceName string + // ServerAddr specifies address of the tunnel server. + ServerAddr string + //Upstream upstream address + Upstream string + //Protocol .. + Protocol string + // TLSClientConfig specifies the tls configuration to use with + // tls.Client. + TLSClientConfig *tls.Config + // Backoff specifies backoff policy on server connection retry. If nil + // when dial fails it will not be retried. + Backoff Backoff + + //ServiceProxy is Func responsible for transferring data between server and local services. + ServiceProxy proxy.Func + // Logger is optional logger. If nil logging is disabled. + Logger *relaylogger.RelayLog +} + +//Client struct +type Client struct { + sync.Mutex + conn net.Conn + httpServer *http2.Server + serverErr error + lastDisconnect time.Time + config *ClientConfig + logger *relaylogger.RelayLog + closeChnl chan bool + streams int64 + lastRequest time.Time + lastRequestStreams int64 + isScaledConnection bool +} + +var ( + clog *relaylogger.RelayLog + //Clients map, key by ServerName + //Clients = make(map[string]*Client) + ScaleClients = make(chan bool, 5) +) + +func expBackoff(c BackoffConfig) *backoff.ExponentialBackOff { + b := backoff.NewExponentialBackOff() + b.InitialInterval = c.Interval + b.Multiplier = c.Multiplier + b.MaxInterval = c.MaxInterval + b.MaxElapsedTime = c.MaxTime + + return b +} + +//loadNewRelayNetwork start the relay agent for a given network +func loadNewRelayNetwork(ctx context.Context, rnc utils.RelayNetworkConfig) error { + var spxy proxy.Func + var tlsconf *tls.Config + var dialout *Dialout + var err error + + backoff := BackoffConfig{ + Interval: DefaultBackoffInterval, + Multiplier: DefaultBackoffMultiplier, + MaxInterval: DefaultBackoffMaxInterval, + MaxTime: DefaultBackoffMaxTime, + } + + //proxy handler + switch rnc.Network.Name { + case utils.CDAGENTCORE: + tlsconf, err = ClientTLSConfigFromBytes(rnc.RelayAgentCert, rnc.RelayAgentKey, rnc.RelayAgentCACert, strings.ReplaceAll(rnc.Network.Domain, "*", utils.AgentID)) + if err != nil { + clog.Error( + err, + "failed to create tlsconfig", + "service name", rnc.Network.Name, + "addr", rnc.Network.Domain, + ) + return fmt.Errorf("failed to load client certs") + } + dialout = &Dialout{ + Protocol: rnc.Network.Name, + Upstream: rnc.Network.Upstream, + Addr: strings.ReplaceAll(rnc.Network.Domain, "*", utils.AgentID), + ServiceSNI: utils.AgentID, + } + spxy = clientProxy(utils.GENTCP, dialout, clog.WithName(rnc.Network.Name)) + default: + tlsconf, err = ClientTLSConfigFromBytes(rnc.RelayAgentCert, rnc.RelayAgentKey, rnc.RelayAgentCACert, strings.ReplaceAll(rnc.Network.Domain, "*", utils.ClusterID)) + if err != nil { + clog.Error( + err, + "failed to create tlsconfig", + "service name", rnc.Network.Name, + "addr", rnc.Network.Domain, + ) + return fmt.Errorf("failed to load client certs") + } + dialout = &Dialout{ + Protocol: rnc.Network.Name, + Upstream: rnc.Network.Upstream, + Addr: strings.ReplaceAll(rnc.Network.Domain, "*", utils.ClusterID), + ServiceSNI: utils.ClusterID, + } + spxy = clientProxy(utils.KUBECTL, dialout, clog.WithName(rnc.Network.Name)) + } + + if spxy == nil { + return fmt.Errorf("failed to load client proxy") + } + + ccfg := &ClientConfig{ + ServiceName: rnc.Network.Name, + ServerAddr: dialout.Addr, + Protocol: dialout.Protocol, + TLSClientConfig: tlsconf, + Upstream: dialout.Upstream, + ServiceProxy: spxy, + Backoff: expBackoff(backoff), + Logger: clog.WithName(rnc.Network.Name), + } + + for i := 0; i < utils.MaxDials; i++ { + c := &Client{ + config: ccfg, + httpServer: &http2.Server{}, + logger: clog.WithName(rnc.Network.Name), + isScaledConnection: false, + } + + c.closeChnl = make(chan bool) + go c.runClient(ctx) + } + + // scale clients + // based on rate of new streams or + // based on total concurrent streams + go runClientScaling(ctx, ccfg, rnc) + + return nil +} + +func runClientScaling(ctx context.Context, ccfg *ClientConfig, rnc utils.RelayNetworkConfig) { + var scaledClients []*Client + totalScaledClient := 0 + + for { + select { + case <-ScaleClients: + // got signal to scale + if totalScaledClient < utils.MaxDials*utils.MaxScaleMultiplier { + c := &Client{ + config: ccfg, + httpServer: &http2.Server{}, + logger: clog.WithName(rnc.Network.Name), + isScaledConnection: true, + } + c.closeChnl = make(chan bool) + scaledClients = append(scaledClients, c) + totalScaledClient++ + go c.runClient(ctx) + clog.Info( + "scale client signal", + " totalScaledClient ", totalScaledClient, + ) + } + case <-time.After(5 * time.Minute): + tempClients := scaledClients[:0] + for _, c := range scaledClients { + now := time.Now() + c.Lock() + curStreams := atomic.LoadInt64(&c.streams) + d := now.Sub(c.lastRequest) + s := int64(d / time.Hour) + if curStreams <= 0 && s >= int64(utils.HealingInterval) { + // no request for last 24Hr, close client + c.closeChnl <- true + totalScaledClient-- + } else { + tempClients = append(tempClients, c) + } + c.Unlock() + } + scaledClients = tempClients + clog.Info( + "scaled client timer", + " totalScaledClient ", totalScaledClient, + ) + case <-ctx.Done(): + return + } + } +} + +func (c *Client) runClient(ctx context.Context) { + if err := c.Start(ctx); err != nil { + if c.isScaledConnection { + c.logger.Info( + "closed scaled dialouts after idle time", + "service name", c.config.ServiceName, + "addr", c.config.ServerAddr, + ) + } else { + c.logger.Error( + err, + "failed to start client for dialouts", + "service name", c.config.ServiceName, + "addr", c.config.ServerAddr, + ) + } + } +} + +//Start relay client +func (c *Client) Start(ctx context.Context) error { + cw := make(chan bool) + + for { + conn, err := c.connect() + if err != nil { + c.logger.Info( + "connect failed will retry after 2 sec", + ) + time.Sleep(2 * time.Second) + continue + } + + go func() { + //handles the http/2 tunnel + c.httpServer.ServeConn(conn, &http2.ServeConnOpts{ + Handler: http.HandlerFunc(c.serveHTTP), + }) + + cw <- true + }() + + select { + case <-cw: + // dialout got closed + time.Sleep(100 * time.Millisecond) + case <-c.closeChnl: + c.conn.Close() + c.logger.Info( + "close connection due to idle streams", + ) + return fmt.Errorf("idle streams") + case <-ctx.Done(): + c.conn.Close() + return fmt.Errorf("ctx done") + } + + c.logger.Info( + "client disconnected", + ) + c.Lock() + now := time.Now() + err = c.serverErr + + // detect disconnect hiccup + if err == nil && now.Sub(c.lastDisconnect).Seconds() < 5 { + err = fmt.Errorf("connection is being cut") + } + + c.conn = nil + c.serverErr = nil + c.lastDisconnect = now + c.Unlock() + + if err != nil { + c.logger.Error( + err, + "disconnected, will retry after 2 sec", + "name", c.config.ServiceName, + ) + time.Sleep(2 * time.Second) + continue + } + } +} + +func (c *Client) processDialoutProxy(conn net.Conn, network, addr string) error { + // send CONNECT addr HTTP/1.1 header + connHeader := "CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n" + if utils.DialoutProxyAuth != "" { + connHeader = connHeader + "Proxy-Authorization:" + utils.DialoutProxyAuth + "\r\n" + } + connHeader = connHeader + "Connection: Keep-Alive\r\n\r\n" + + conn.Write([]byte(connHeader)) + tmp := make([]byte, 1) + checkHeader := "" + startAppend := false + respData := "" + for { + n, err := conn.Read(tmp) + if n != 1 || err != nil { + c.logger.Info( + "dial failed ", + "proxy", utils.DialoutProxy, + "proxy header", connHeader, + "network", network, + "checkHeader", checkHeader, + "err", err, + ) + return fmt.Errorf("proxy read error") + } + respData += string(tmp[0]) + if tmp[0] == '\r' { + if startAppend { + checkHeader += string(tmp[0]) + } else { + checkHeader = "\r" + startAppend = true + } + } else if tmp[0] == '\n' { + if startAppend { + if checkHeader == "\r\n\r" { + // prased the proxy response header + break + } + checkHeader += string(tmp) + } + } else { + startAppend = false + checkHeader = "" + } + } + c.logger.Info( + "proxy dialout success", + "proxy resp", respData, + ) + return nil +} + +//dialout connect +func (c *Client) connect() (net.Conn, error) { + c.Lock() + defer c.Unlock() + + if c.conn != nil { + return nil, fmt.Errorf("already connected") + } + + conn, err := c.dial() + if err != nil { + return nil, fmt.Errorf("failed to connect to server %s error %s", c.config.ServerAddr, err) + } + c.conn = conn + + return conn, nil +} + +//dial +func (c *Client) dial() (net.Conn, error) { + var ( + network = "tcp" + addr = c.config.ServerAddr + tlsConfig = c.config.TLSClientConfig + ) + + doDial := func() (conn net.Conn, err error) { + + d := &net.Dialer{ + Timeout: 60 * time.Second, + } + if utils.DialoutProxy == "" { + conn, err = d.Dial(network, addr) + } else { + conn, err = d.Dial(network, utils.DialoutProxy) + if err == nil { + err = c.processDialoutProxy(conn, network, addr) + } + } + + if err == nil { + err = utils.KeepAlive(conn) + } + if err == nil { + conn = tls.Client(conn, tlsConfig) + } + + if err == nil { + err = conn.(*tls.Conn).Handshake() + } + + if err != nil { + if conn != nil { + conn.Close() + conn = nil + } + + c.logger.Info( + "dial failed", + "network", network, + "addr", addr, + "err", err, + ) + } + + c.logger.Info( + "action dial out", + "network", network, + "addr", addr, + ) + + return + } + + b := c.config.Backoff + if b == nil { + // try once and return + return doDial() + } + + for { + + conn, err := doDial() + + // success + if err == nil { + b.Reset() + c.logger.Info( + "dial success", + ) + return conn, err + } + + // failure + d := b.NextBackOff() + if d < 0 { + b.Reset() + return conn, fmt.Errorf("backoff limit exeded: %s", err) + } + + // backoff + c.logger.Info( + "action backoff", + "sleep", d, + "address", c.config.ServerAddr, + ) + time.Sleep(d) + } +} + +// tunnel serveHTTP handler. Requests form relay lands here. +// request handled based on the action in the ctrl message (header) +// Connect method is used as handshake +// PUT method is used for tunneled requests that carry +// stream data as req.Body +func (c *Client) serveHTTP(w http.ResponseWriter, r *http.Request) { + c.logger.Debug( + "invoked handler serveHTTP", + "method", r.Method, + "currentStreams", atomic.LoadInt64(&c.streams), + ) + + if r.Method == http.MethodConnect { + if r.Header.Get(utils.HeaderError) != "" { + c.handleHandshakeError(w, r) + } else { + c.handleHandshake(w, r) + } + return + } + + atomic.AddInt64(&c.streams, 1) + defer atomic.AddInt64(&c.streams, ^int64(0)) + + curStreams := atomic.LoadInt64(&c.streams) + now := time.Now() + + c.Lock() + d := now.Sub(c.lastRequest) + s := int64(d / time.Second) + if s >= 4 { // check in minimum 4 sec + if !c.isScaledConnection { + if curStreams > int64(utils.ScalingStreamsThreshold) { + // concurrent streams count is high + ScaleClients <- true + } else if curStreams > c.lastRequestStreams { + rate := (curStreams - c.lastRequestStreams) / s + if rate > int64(utils.ScalingStreamsRateThreshold) { + // rate of new stream is high + ScaleClients <- true + } + } + } + c.lastRequest = now + c.lastRequestStreams = curStreams + } + c.Unlock() + + msg, err := utils.ReadControlMessage(r) + if err != nil { + c.logger.Error( + err, + "Read Control Message failed", + ) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + c.logger.Debug( + "handle proxy action", + "ctrlMsg", msg, + "req", r, + "currentStreams", atomic.LoadInt64(&c.streams), + ) + + switch msg.Action { + case utils.ActionProxy: + c.config.ServiceProxy(w, r.Body, msg, r) + default: + c.logger.Info( + "unknown action", + "ctrlMsg", msg, + ) + http.Error(w, err.Error(), http.StatusBadRequest) + } +} + +func (c *Client) handleHandshakeError(w http.ResponseWriter, r *http.Request) { + err := fmt.Errorf(r.Header.Get(utils.HeaderError)) + + c.logger.Info( + "action handshake error", + "addr", r.RemoteAddr, + "err", err, + ) + + c.Lock() + c.serverErr = fmt.Errorf("server error: %s", err) + c.Unlock() +} + +func (c *Client) handleHandshake(w http.ResponseWriter, r *http.Request) { + + c.logger.Info( + "action", "handshake", + "addr", r.RemoteAddr, + ) + + w.WriteHeader(http.StatusOK) + host, _, _ := net.SplitHostPort(c.config.ServerAddr) + msg := handShakeMsg{ + ServiceName: c.config.ServiceName, + Protocol: c.config.Protocol, + Host: host, + } + b, err := json.Marshal(msg) + if err != nil { + c.logger.Info( + "msg", "handshake failed", + "err", err, + ) + return + } + w.Write(b) +} + +//setups the proxy func handler +func clientProxy(svcName string, d *Dialout, logger *relaylogger.RelayLog) proxy.Func { + proxyCfg := &utils.ProxyConfig{ + Protocol: d.Protocol, + Addr: d.Addr, + ServiceSNI: d.ServiceSNI, + RootCA: d.RootCA, + ClientCRT: d.ClientCRT, + ClientKEY: d.ClientKEY, + Upstream: d.Upstream, + UpstreamClientCRT: d.UpstreamClientCRT, + UpstreamClientKEY: d.UpstreamClientKEY, + UpstreamRootCA: d.UpstreamRootCA, + UpstreamSkipVerify: d.UpstreamSkipVerify, + UpstreamKubeConfig: d.UpstreamKubeConfig, + Version: d.Version, + } + + switch svcName { + case utils.KUBECTL: + if p := proxy.NewKubeCtlTCPProxy(logger.WithName("TCPProxy"), proxyCfg); p != nil { + return p.Proxy + } + logger.Error( + nil, + "proxy.NewKubeCtlTCPProxy returned nil", + ) + return nil + case utils.GENTCP: + if p := proxy.NewTCPProxy(logger.WithName("TCPProxy"), proxyCfg); p != nil { + return p.Proxy + } + logger.Error( + nil, + "proxy.NewTCPProxy returned nil", + ) + return nil + default: + logger.Error( + nil, + "unknown service name", + ) + return nil + } +} + +//StartClient starts relay clients +func StartClient(ctx context.Context, log *relaylogger.RelayLog, file string, rnc utils.RelayNetworkConfig, exitChan chan<- bool) { + clog = log.WithName("Client") + + if err := loadNewRelayNetwork(ctx, rnc); err != nil { + clog.Error(err, "failed to load relay network") + exitChan <- true + return + } + + for { + select { + case <-ctx.Done(): + clog.Error( + ctx.Err(), + "Stopping client", + ) + return + } + } +} diff --git a/components/relay/pkg/tunnel/client_config.go b/components/relay/pkg/tunnel/client_config.go new file mode 100644 index 0000000..02107cf --- /dev/null +++ b/components/relay/pkg/tunnel/client_config.go @@ -0,0 +1,38 @@ +package tunnel + +import ( + "time" +) + +// Default backoff configuration. +const ( + DefaultBackoffInterval = 500 * time.Millisecond + DefaultBackoffMultiplier = 1.5 + DefaultBackoffMaxInterval = 20 * time.Second + DefaultBackoffMaxTime = 2 * time.Minute +) + +// BackoffConfig defines behavior of staggering reconnection retries. +type BackoffConfig struct { + Interval time.Duration + Multiplier float64 + MaxInterval time.Duration + MaxTime time.Duration +} + +// Dialout defines the dialout. +type Dialout struct { + Protocol string + Addr string + ServiceSNI string + RootCA string + ClientCRT string + ClientKEY string + Upstream string + UpstreamClientCRT string + UpstreamClientKEY string + UpstreamRootCA string + UpstreamSkipVerify bool + UpstreamKubeConfig string + Version string +} diff --git a/components/relay/pkg/tunnel/client_test.go b/components/relay/pkg/tunnel/client_test.go new file mode 100644 index 0000000..a5cc9f9 --- /dev/null +++ b/components/relay/pkg/tunnel/client_test.go @@ -0,0 +1,70 @@ +package tunnel + +import ( + "fmt" + "net" + "sync" + "testing" + "time" +) + +const ( + //UNIXSOCKET .. + UNIXSOCKET = "/tmp/relay-unix-" +) + +func startClient(wg *sync.WaitGroup, buf []byte, t *testing.T) { + defer wg.Done() + + fmt.Print("starting client") + buf3 := make([]byte, 4096) + // connect to this socket + socketPath := UNIXSOCKET + "kubectldialin.relay.rafay.dev" + conn, err := net.DialTimeout("unix", socketPath, 60*time.Second) + if err != nil { + t.Error("failed to connect ", err) + return + } + + fmt.Println("Connected to ", socketPath) + + for { + conn.Write(buf) + str := "GET /apis/rbac.authorization.k8s.io/v1?timeout=32s HTTP/1.1\r\nHost: 192.168.56.103:6443\r\nUser-Agent: curl/7.54.0\r\nAccept: */*\r\nX-Rafay-User:namespace-admin-sa\r\nX-Rafay-Namespace: default\r\n\r\n" + conn.Write([]byte(str)) + + conn.SetReadDeadline(time.Now().Add(10 * (time.Second))) + + for { + nr, err := conn.Read(buf3) + if err != nil { + fmt.Println("done reading ", err) + return + } + data := buf3[0:nr] + println("Client got:", string(data), nr) + } + } +} + +func TestRelayClient(t *testing.T) { + var wg sync.WaitGroup + + buf := make([]byte, 1024) + num := 1 + key := "cluster1.kubectldialin.relay.rafay.dev-ABCD" + + message := fmt.Sprintf("{\"DialinKey\": \"%s\", \"UserName\": \"namespace-admin-sa\", \"SNI\": \"cluster1.kubectldialin.relay.rafay.dev\"}", key[:len(key)-1]) + copy(buf, message) + + for i := 0; i < num; i++ { + wg.Add(1) + fmt.Println("starting client") + go startClient(&wg, buf, t) + } + + fmt.Print("Waiting for workers") + wg.Wait() + + fmt.Print("Completed") +} diff --git a/components/relay/pkg/tunnel/common.go b/components/relay/pkg/tunnel/common.go new file mode 100644 index 0000000..472fcd2 --- /dev/null +++ b/components/relay/pkg/tunnel/common.go @@ -0,0 +1,189 @@ +package tunnel + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" +) + +type handShakeMsg struct { + ServiceName string + Protocol string + Host string +} + +var ( + //PeerProbeChanel to push cluster connection probe. + //The RPC picks the sni and sends to core + PeerProbeChanel = make(chan string, 256) + emptyID [32]byte +) + +//ServerTLSConfigFromBytes prepare a tls config from cert,key,rootCA +func ServerTLSConfigFromBytes(certList []utils.SNICertificate, rootCAs []string, nextprotos ...string) (*tls.Config, error) { + var err error + + // load certs + config := &tls.Config{} + config.Certificates = make([]tls.Certificate, len(certList)) + + for i, v := range certList { + config.Certificates[i], err = tls.X509KeyPair(v.CertFile, v.KeyFile) + if err != nil { + return nil, err + } + } + + config.BuildNameToCertificate() + + // load rootCAs for client authentication + clientAuth := tls.RequireAndVerifyClientCert + var roots *x509.CertPool + if len(rootCAs) > 0 { + roots = x509.NewCertPool() + + for _, rootCA := range rootCAs { + rootPEM := []byte(rootCA) + if len(rootPEM) > 0 { + if ok := roots.AppendCertsFromPEM(rootPEM); !ok { + return nil, err + } + } + } + + clientAuth = tls.RequireAndVerifyClientCert + } + + config.ClientAuth = clientAuth + config.ClientCAs = roots + config.SessionTicketsDisabled = true + config.MinVersion = tls.VersionTLS12 + config.CipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} + config.PreferServerCipherSuites = true + if len(nextprotos) > 0 { + config.NextProtos = nextprotos + } else { + config.NextProtos = []string{"h1", "h2"} + } + + return config, nil +} + +//ClientTLSConfigFromBytes sets tls config +func ClientTLSConfigFromBytes(tlsCrt []byte, tlsKey []byte, rootPEM []byte, addr string) (*tls.Config, error) { + cert, err := tls.X509KeyPair(tlsCrt, tlsKey) + if err != nil { + return nil, err + } + + var roots *x509.CertPool + if len(rootPEM) > 0 { + roots = x509.NewCertPool() + if ok := roots.AppendCertsFromPEM(rootPEM); !ok { + return nil, err + } + } + + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return &tls.Config{ + ServerName: host, + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: roots == nil, + RootCAs: roots, + SessionTicketsDisabled: true, + MinVersion: tls.VersionTLS12, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + PreferServerCipherSuites: true, + }, nil +} + +//ClientTLSConfig sets tls config +func ClientTLSConfig(tlsCrt string, tlsKey string, rootCA string, addr string) (*tls.Config, error) { + + cert, err := tls.LoadX509KeyPair(tlsCrt, tlsKey) + if err != nil { + return nil, err + } + + var roots *x509.CertPool + if rootCA != "" { + roots = x509.NewCertPool() + rootPEM, err := ioutil.ReadFile(rootCA) + if err != nil { + return nil, err + } + if ok := roots.AppendCertsFromPEM(rootPEM); !ok { + return nil, err + } + } + + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return &tls.Config{ + ServerName: host, + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: roots == nil, + RootCAs: roots, + SessionTicketsDisabled: true, + MinVersion: tls.VersionTLS12, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, + PreferServerCipherSuites: true, + }, nil +} + +//GetRemoteCertID extract peer ID +func GetRemoteCertID(conn *tls.Conn) (string, error) { + // Try a TLS connection over the given connection. We explicitly perform + // the handshake, since we want to maintain the invariant that, if this + // function returns successfully, then the connection should be valid + // and verified. + if err := conn.Handshake(); err != nil { + return "", err + } + + cs := conn.ConnectionState() + + // We should have exactly one peer certificate. + certs := cs.PeerCertificates + if cl := len(certs); cl != 1 { + return "", fmt.Errorf("expecting 1 peer certificate got %d", cl) + } + + // Get remote cert's ID. + remoteCert := certs[0] + //remoteID := New(remoteCert.Raw) + + return remoteCert.Subject.CommonName, nil +} + +func SendPeerProbe(chnl chan<- string, clustersni string) { + attempt := 0 + select { + case chnl <- clustersni: + return + default: + attempt++ + if attempt > 3 { + return + } + time.Sleep(5 * time.Second) + } +} diff --git a/components/relay/pkg/tunnel/dialin_pool.go b/components/relay/pkg/tunnel/dialin_pool.go new file mode 100644 index 0000000..993fd27 --- /dev/null +++ b/components/relay/pkg/tunnel/dialin_pool.go @@ -0,0 +1,381 @@ +package tunnel + +import ( + "context" + "fmt" + "net" + "net/http" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "golang.org/x/net/http2" +) + +var _dplog *relaylogger.RelayLog + +type connPair struct { + conn net.Conn + clientConn *http2.ClientConn +} + +type dialinConnector struct { + // this is list of dialin peer ids + connKeys []string + lbIndex int +} +type dialinPool struct { + t *http2.Transport + conns map[string]connPair // key is host:port + free func(string) + mu sync.RWMutex + //map of connector IDs with dialin SNI key + dialinConnectors map[string]*dialinConnector +} + +func newDialinPool(t *http2.Transport, f func(string), log *relaylogger.RelayLog) *dialinPool { + return &dialinPool{ + t: t, + free: f, + conns: make(map[string]connPair), + dialinConnectors: make(map[string]*dialinConnector), + } +} + +func (p *dialinPool) URL(key string) string { + return fmt.Sprint("https://", key) +} + +//GetClientConn get connector +func (p *dialinPool) GetClientConn(req *http.Request, addr string) (*http2.ClientConn, error) { + p.mu.RLock() + defer p.mu.RUnlock() + + if cp, ok := p.conns[addr]; ok && cp.clientConn.CanTakeNewRequest() { + return cp.clientConn, nil + } + + return nil, errClientNotConnected +} + +// CheckDialinKeyExist check cached key's still exist in pool +func (p *dialinPool) CheckDialinKeyExist(key string) bool { + p.mu.RLock() + defer p.mu.RUnlock() + _, ok := p.conns[key] + return ok +} + +func (p *dialinPool) MarkDead(c *http2.ClientConn) { + p.mu.Lock() + defer p.mu.Unlock() + + for addr, cp := range p.conns { + if cp.clientConn == c { + _dplog.Debug("MarkDead", addr, p) + p.close(cp, addr) + s := strings.SplitAfter(addr, utils.JoinString) + if len(s) == 3 { + sni := strings.Trim(s[0], utils.JoinString) + addr := s[1] + s[2] + if !p.deleteDialinConnectorKey(sni, addr) { + _dplog.Error(nil, "error in dialin MarkDead connector key delete did not find key ", addr) + } + } + return + } + } +} + +func (p *dialinPool) AddConn(conn net.Conn, identifier string, sni string, remoteAddr string) (string, error) { + p.mu.Lock() + defer p.mu.Unlock() + + // append peer-cert-id with remoteAddr + addr := p.addr(identifier) + utils.JoinString + remoteAddr + + // prepend dialin sni to addr to construct the key + key := sni + utils.JoinString + addr + + if cp, ok := p.conns[key]; ok { + // check conns has the key + if err := p.ping(cp); err != nil { + p.close(cp, key) + } else { + return "", fmt.Errorf("connector key %s already in dialin pool ", key) + } + } + + // create transport new connection + c, err := p.t.NewClientConn(conn) + if err != nil { + return "", err + } + + // add conn to conns + p.conns[key] = connPair{ + conn: conn, + clientConn: c, + } + + go sendHeartBeats(p.conns[key]) + + // set dialinConnectors map using key as sni + if item, ok := p.dialinConnectors[sni]; ok { + itemCount := len(item.connKeys) + if itemCount <= 0 { + p.dialinConnectors[sni].connKeys = append(p.dialinConnectors[sni].connKeys, addr) + sort.Strings(p.dialinConnectors[sni].connKeys) + } else { + index := sort.SearchStrings(p.dialinConnectors[sni].connKeys, addr) + if index < itemCount && p.dialinConnectors[sni].connKeys[index] == addr { + _dplog.Info("address already exist") + } else { + //item not found + p.dialinConnectors[sni].connKeys = append(p.dialinConnectors[sni].connKeys, addr) + sort.Strings(p.dialinConnectors[sni].connKeys) + } + } + } else { + p.dialinConnectors[sni] = &dialinConnector{ + lbIndex: 0, + } + p.dialinConnectors[sni].connKeys = append(p.dialinConnectors[sni].connKeys, addr) + sort.Strings(p.dialinConnectors[sni].connKeys) + } + + _dplog.Info( + "Added dialin connection", + "addr", addr, + "key", key, + ) + return key, nil +} + +//GetDialinConnectorKey get connector key +func (p *dialinPool) GetDialinConnectorCount(sni string) (int, error) { + p.mu.RLock() + defer p.mu.RUnlock() + + _dplog.Debug( + "GetDialinConnectorCount", + "sni", sni, + "pool", p, + ) + + _dplog.Info("GetDialinConnectorCount", p.dialinConnectors) + if item, ok := p.dialinConnectors[sni]; ok { + itemCount := len(item.connKeys) + if itemCount > 0 { + return itemCount, nil + } + } + + return 0, fmt.Errorf("Empty dialin pool.dialinConnectors for sni %s ", sni) +} + +func (p *dialinPool) getConnKey(sni string, item *dialinConnector, count int) (string, error) { + for i := 0; i < count; i++ { + key := sni + utils.JoinString + item.connKeys[i] + if _, ok := p.conns[key]; ok { + return key, nil + } + } + + return "", fmt.Errorf("Empty dialin pool.dialinConnectors for sni %s ", sni) +} + +//GetDialinConnectorKey get connector key +func (p *dialinPool) GetDialinConnectorKey(sni string) (string, error) { + p.mu.Lock() + defer p.mu.Unlock() + + _dplog.Info( + "GetDialinConnectorKey", + "sni", sni, + ) + + if item, ok := p.dialinConnectors[sni]; ok { + itemCount := len(item.connKeys) + + if itemCount <= 0 { + return "", fmt.Errorf("Empty dialin pool.dialinConnectors for sni %s ", sni) + } + + //simple round robin selection + if item.lbIndex >= itemCount { + item.lbIndex = 0 + } + + key := sni + utils.JoinString + item.connKeys[item.lbIndex] + item.lbIndex++ + + // check key + if _, ok := p.conns[key]; !ok { + // get any key + return p.getConnKey(sni, item, itemCount) + } + + // key is valid + return key, nil + } + + return "", fmt.Errorf("Empty dialin pool.dialinConnectors for sni %s ", sni) +} + +func (p *dialinPool) deleteDialinConnectorKey(sni, addr string) bool { + if item, ok := p.dialinConnectors[sni]; ok { + itemCount := len(item.connKeys) + index := sort.SearchStrings(p.dialinConnectors[sni].connKeys, addr) + if index < itemCount { + // Not expecting lots of connectors. So using slower delete opeartion + // by shifting elements to preserve the sorted orders + p.dialinConnectors[sni].connKeys = append(item.connKeys[:index], item.connKeys[index+1:]...) + _dplog.Info( + "Deleted Connnection from dialin pool", + "addr", addr, + ) + if len(p.dialinConnectors[sni].connKeys) <= 0 { + _dplog.Info( + "Deleted last connection from dialin pool", + "sni", sni, + "addr", addr, + ) + delete(p.dialinConnectors, sni) + } + return true + } + } + return false +} + +func (p *dialinPool) DeleteConn(identifier string, sni string, remoteAddr string) { + p.mu.Lock() + defer p.mu.Unlock() + + // append peer-cert-id with remoteAddr + addr := identifier + utils.JoinString + remoteAddr + + // prepend dialin sni to addr to construct the key + key := sni + utils.JoinString + addr + if cp, ok := p.conns[key]; ok { + p.close(cp, key) + } else { + _dplog.Error( + nil, + "did not find key in pool.Conns", + "key", key, + "pool", p, + ) + } + + if p.deleteDialinConnectorKey(sni, addr) { + return + } + + _dplog.Info( + "DeleteConn did not find the coonection", + "sni", sni, + "addr", addr, + "key", key, + "pool", p, + ) + +} + +func (p *dialinPool) Ping(identifier string) (time.Duration, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if cp, ok := p.conns[identifier]; ok { + start := time.Now() + err := p.ping(cp) + return time.Since(start), err + } + + return 0, errClientNotConnected +} + +// heart beats to keep dialins alive to avoid idletimeout +func sendHeartBeats(cp connPair) { + for { + ctx, cancel := context.WithTimeout(context.Background(), utils.DefaultPingTimeout) + if err := cp.clientConn.Ping(ctx); err != nil { + cancel() + _dplog.Debug("dialin keep-alive ping err", err) + return + } + _dplog.Debug("dialin keep-alive ping success") + cancel() + time.Sleep(30 * time.Second) + } +} + +func (p *dialinPool) ping(cp connPair) error { + ctx, cancel := context.WithTimeout(context.Background(), utils.DefaultPingTimeout) + defer cancel() + + return cp.clientConn.Ping(ctx) +} + +func (p *dialinPool) close(cp connPair, addr string) { + cp.conn.Close() + _dplog.Info("dialin close", addr) + delete(p.conns, addr) + if p.free != nil { + p.free(addr) + } +} + +func (p *dialinPool) addr(identifier string) string { + return identifier +} + +//StartDialinPoolMgr starting dialin connection manager +func StartDialinPoolMgr(ctx context.Context, log *relaylogger.RelayLog, exitChan chan<- bool) { + _dplog = log.WithName("DialinPool") + + for { + select { + case <-ctx.Done(): + slog.Error( + ctx.Err(), + "Stopping dialin pool manager", + ) + return + } + } +} + +//GetDialinMetrics get connector key +func (p *dialinPool) GetDialinMetrics(w http.ResponseWriter) { + var clusterCnt, connCnt int + clusterCnt, connCnt = 0, 0 + + p.mu.RLock() + defer p.mu.RUnlock() + + _dplog.Info( + "GetDialinMetrics", + ) + + fmt.Fprintf(w, "{\"dialinmetrics\": [") + for sni, item := range p.dialinConnectors { + clusterCnt++ + connCnt += len(item.connKeys) + itemCount := strconv.Itoa(len(item.connKeys)) + + fmt.Fprintf(w, "{\"cluster\": \""+sni+"\", \"connections\": \""+itemCount+"\"},") + + } + + fmt.Fprintf(w, "], \"totalclusters\": "+strconv.Itoa(clusterCnt)) + fmt.Fprintf(w, ", \"totalconnections\": "+strconv.Itoa(connCnt)) + fmt.Fprintf(w, ", \"podname\": "+utils.PODNAME) + + fmt.Fprintf(w, " }") +} diff --git a/components/relay/pkg/tunnel/errors.go b/components/relay/pkg/tunnel/errors.go new file mode 100644 index 0000000..73eff17 --- /dev/null +++ b/components/relay/pkg/tunnel/errors.go @@ -0,0 +1,11 @@ +package tunnel + +import "errors" + +var ( + errClientNotSubscribed = errors.New("client not subscribed") + errClientNotConnected = errors.New("client not connected") + errClientAlreadyConnected = errors.New("client already connected") + + errUnauthorised = errors.New("unauthorised") +) diff --git a/components/relay/pkg/tunnel/peer.go b/components/relay/pkg/tunnel/peer.go new file mode 100644 index 0000000..18d2e17 --- /dev/null +++ b/components/relay/pkg/tunnel/peer.go @@ -0,0 +1,168 @@ +package tunnel + +import ( + "context" + "crypto/tls" + "net/url" + + "time" + + comgrpc "github.com/RafaySystems/rcloud-base/components/common/pkg/grpc" + peerclient "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/peering" + relayrpc "github.com/RafaySystems/rcloud-base/components/common/proto/rpc/sentry" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "google.golang.org/grpc/credentials" +) + +var peerlog *relaylogger.RelayLog + +//StartPeeringMgr will start the peering RPCs +func StartPeeringMgr(ctx context.Context, log *relaylogger.RelayLog, exitChan chan<- bool, config *ServerConfig) { + var tlsConfig *tls.Config + var err error + var peerSeviceHost string + + peerlog = log.WithName("PeeringMgr") + + if utils.PeerCache == nil { + peerlog.Error( + nil, + "peer client cache is nil", + ) + exitChan <- true + return + } + + //Peer Service Clint certs is popultaed from boot strap use it + if len(utils.PeerCertificate) > 0 && len(utils.PeerPrivateKey) > 0 && len(utils.PeerCACertificate) > 0 { + u, err := url.Parse(utils.PeerServiceURI) + if err != nil { + peerlog.Error( + nil, + "peer servic uri parse failed", + ) + exitChan <- true + return + } + //Load certificates + tlsConfig, err = ClientTLSConfigFromBytes(utils.PeerCertificate, utils.PeerPrivateKey, utils.PeerCACertificate, u.Host) + peerSeviceHost = u.Host + } else { + //Load certificates + tlsConfig, err = ClientTLSConfig(config.Controller.ClientCRT, config.Controller.ClientKEY, config.Controller.RootCA, config.Controller.PeerProbeSNI) + peerSeviceHost = config.Controller.PeerProbeSNI + } + + if err != nil { + peerlog.Error( + err, + "Error loading peer TLC config", + ) + exitChan <- true + return + } + + //enforce TLS mutual authN + transportCreds := credentials.NewTLS(tlsConfig) + +connectRetry: + log.Debug("Start Peer grpc dial") + conn, err := comgrpc.NewSecureClientConn(ctx, peerSeviceHost, transportCreds) + if err != nil { + peerlog.Error( + err, + "failed to connect to peer server, retry in 10 seconds", + peerSeviceHost, + ) + time.Sleep(10 * time.Second) + goto connectRetry + } + + log.Info("grpc connected to peer service", peerSeviceHost) + client := relayrpc.NewRelayPeerServiceClient(conn) + + // create RPC streams + helloStream, err := client.RelayPeerHelloRPC(context.Background()) + if err != nil { + peerlog.Error( + err, + "failed to create HelloRPC stream with peer server, retry in 10 seconds", + peerSeviceHost, + ) + conn.Close() + time.Sleep(10 * time.Second) + goto connectRetry + } + + probeStream, err := client.RelayPeerProbeRPC(context.Background()) + if err != nil { + peerlog.Error( + err, + "failed to create ProbeRPC stream with peer server, retry in 10 seconds", + peerSeviceHost, + ) + conn.Close() + time.Sleep(10 * time.Second) + goto connectRetry + } + + surveyStream, err := client.RelayPeerSurveyRPC(context.Background()) + if err != nil { + peerlog.Error( + err, + "failed to create SurveyRPC stream with peer server, retry in 10 seconds", + peerSeviceHost, + ) + conn.Close() + time.Sleep(10 * time.Second) + goto connectRetry + } + + peerlog.Debug( + "created RPC streams with peer server", + peerSeviceHost, + ) + + rpcctx, rpccancel := context.WithCancel(context.Background()) + go peerclient.ClientHelloRPC(rpcctx, helloStream, utils.PeerHelloInterval, utils.RelayUUID, utils.GetRelayIP) + //Add a init time wait for hello stream to finish + time.Sleep(2 * time.Second) + + go peerclient.ClientProbeRPC(rpcctx, probeStream, utils.PeerCache, utils.RelayUUID, utils.PeerCacheDefaultExpiry, PeerProbeChanel, utils.GetRelayIPPort) + + go peerclient.ClientSurveyRPC(rpcctx, surveyStream, utils.RelayUUID, utils.GetRelayIPPort, dialinCountLookup) + + for { + //Watch for errors in rpc streams. + //On error cancel the streams and reconnect. + //HelloRPC send heartbeats in every 60Sec. + //If there is underlying connectivity issues + //HelloRPC will detect it with in 60Sec, + select { + case <-helloStream.Context().Done(): + rpccancel() + conn.Close() + time.Sleep(5 * time.Second) + goto connectRetry + case <-probeStream.Context().Done(): + rpccancel() + conn.Close() + time.Sleep(5 * time.Second) + goto connectRetry + case <-surveyStream.Context().Done(): + rpccancel() + conn.Close() + time.Sleep(5 * time.Second) + goto connectRetry + case <-ctx.Done(): + rpccancel() + slog.Error( + ctx.Err(), + "Stopping PeeringMgr", + ) + return + } + } + +} diff --git a/components/relay/pkg/tunnel/server.go b/components/relay/pkg/tunnel/server.go new file mode 100644 index 0000000..86334b6 --- /dev/null +++ b/components/relay/pkg/tunnel/server.go @@ -0,0 +1,2052 @@ +// Copyright (C) 2020 Rafay Systems https://rafay.co/ + +package tunnel + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/proxy" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/sessions" + + peerclient "github.com/RafaySystems/rcloud-base/components/common/pkg/sentry/peering" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/audit" + "github.com/RafaySystems/rcloud-base/components/relay/pkg/utils" + "github.com/inconshreveable/go-vhost" + "golang.org/x/net/http2" +) + +// Server server definition +type Server struct { + // Type of the server. Relay means user-facing + // Dialin means cluster-facing + Type string + + // Name specifies the service names example kubectl, kubeweb, etc. + Name string + + // ServerName of the listening server. + ServerName string + + // Protocol specifies protocol used http(s) + Protocol string + + // RootCA used to verify TLS client connections + RootCA []byte + + // ServerCRT used for the server + ServerCRT []byte + + // ServerKEY used for the server + ServerKEY []byte + + // DialinServerName specify the dialin server name + // valid only for relay server types. + DialinServerName string + + // ProbeSNI specifies sni used to find peers + //ProbeSNI string + + // DialinPool where dialin connections are parked + // valid only for dialin server types + DialinPool *dialinPool + + // httpClient used to forward users connections to dialin + httpClient *http.Client + + // httpServer used to server user connections + httpServer *http.Server + + Provisioner *authzProvisioner + + auditPath string +} + +//RelayConn connection info +type RelayConn struct { + // Conn is the network connection + Conn net.Conn + + // Type of the server. Relay means user-facing + // Dialin means cluster-facing + Type string + + // ServerName of the server which accepted the connection + ServerName string + + // CertSNI derived from client certificate + CertSNI string + + // PeerID derived from client certificate + PeerID string + + // server block of this connection + server *Server + + // dialinCachedKey already stiched to a dialin + dialinCachedKey string +} + +//ServerListen defines a listen object +type ServerListen struct { + // Addr specifies the listen address + Addr string + // Protocol of all servers listening in above address + Protocol string + // RootCAs used to verify TLS client connections + RootCAs []string + // List of certs used to terminate listening *.format supported + Certs []utils.SNICertificate + // ServerList are the servers using above listen address + ServerList []*Server + + //Mux tls sni muxer + Mux *vhost.TLSMuxer +} + +type kubeError struct { + Kind string `json:"kind"` + APIVersion string `json:"apiVersion"` + Metadata []string `json:"metadata"` + Status string `json:"status"` + Message string `json:"message"` + Reason string `json:"reason"` + Code int `json:"code"` +} + +var ( + //Config server config + Config *ServerConfig + //Servers map, key by ServerName + Servers = make(map[string]*Server) + //ServerAddrs Servers grouped with listen address + ServerAddrs = make(map[string][]*Server) + //ServerListeners list of listen objects + ServerListeners []*ServerListen + + // connections list of relay net.Conn/tls.Conn ojects + connections = make(map[string]*RelayConn) + // connectionsRWMu read write lock for the Connections map + connectionsRWMu sync.RWMutex + + slog *relaylogger.RelayLog +) + +func (srv *Server) disconnected(addr string) { + s := strings.Split(addr, utils.JoinString) + if len(s) < 3 { + slog.Error( + fmt.Errorf("error from disconnected callback invalid key"), + addr, + ) + return + } + remoteAddr := s[2] + if remoteAddr != "" { + deleteConnections(remoteAddr) + } +} + +// loadRelayServers from configuration +// This creates a server objects and add to +// Servers map with key as server name (*.DNS) +func loadRelayServers(config *ServerConfig) { + + slog.Info("Loading Servers form config") + + // Process config to prepare kubectl relay servers + for name, relay := range config.Relays { + provisioner, err := newAuthzProvisioner() + if err != nil { + slog.Error(err, "Unable to create provisioned") + return + } + Servers[relay.ServerName] = &Server{ + Type: utils.RELAY, + Name: name, + ServerName: relay.ServerName, + RootCA: relay.RootCA, + ServerCRT: relay.ServerCRT, + ServerKEY: relay.ServerKEY, + Protocol: relay.Protocol, + DialinServerName: relay.DialinSfx, + httpServer: &http.Server{}, + Provisioner: provisioner, + auditPath: config.AuditPath, + } + + if len(ServerAddrs[relay.Addr]) > 0 { + // check protocol + if ServerAddrs[relay.Addr][0].Protocol == relay.Protocol { + ServerAddrs[relay.Addr] = append(ServerAddrs[relay.Addr], Servers[relay.ServerName]) + } else { + // all servers listening on same address should have same + // protocol + slog.Info( + "Protocol missmatch for same addresses in relay", + "name", name, + "address", relay.Addr, + "protocol", relay.Protocol, + "expected", ServerAddrs[relay.Addr][0].Protocol, + ) + } + } else { + ServerAddrs[relay.Addr] = append(ServerAddrs[relay.Addr], Servers[relay.ServerName]) + } + } + + // Process config to prepare cd relay servers + for name, relay := range config.CDRelays { + Servers[relay.ServerName] = &Server{ + Type: utils.CDRELAY, + Name: name, + ServerName: relay.ServerName, + RootCA: relay.RootCA, + ServerCRT: relay.ServerCRT, + ServerKEY: relay.ServerKEY, + Protocol: relay.Protocol, + DialinServerName: relay.DialinSfx, + auditPath: config.AuditPath, + } + + if len(ServerAddrs[relay.Addr]) > 0 { + // check protocol + if ServerAddrs[relay.Addr][0].Protocol == relay.Protocol { + ServerAddrs[relay.Addr] = append(ServerAddrs[relay.Addr], Servers[relay.ServerName]) + } else { + // all servers listening on same address should have same + // protocol + slog.Info( + "Protocol missmatch for same addresses in relay", + "name", name, + "address", relay.Addr, + "protocol", relay.Protocol, + "expected", ServerAddrs[relay.Addr][0].Protocol, + ) + } + } else { + ServerAddrs[relay.Addr] = append(ServerAddrs[relay.Addr], Servers[relay.ServerName]) + } + } + + // Process config to prepare servers + for name, dialin := range config.Dialins { + s := &Server{ + Type: utils.DIALIN, + Name: name, + ServerName: dialin.ServerName, + RootCA: dialin.RootCA, + ServerCRT: dialin.ServerCRT, + ServerKEY: dialin.ServerKEY, + Protocol: dialin.Protocol, + } + + t := &http2.Transport{} + pool := newDialinPool(t, s.disconnected, slog) + t.ConnPool = pool + s.DialinPool = pool + s.httpClient = &http.Client{ + Transport: t, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + Servers[dialin.ServerName] = s + + if len(ServerAddrs[dialin.Addr]) > 0 { + // check protocol + if ServerAddrs[dialin.Addr][0].Protocol == dialin.Protocol { + ServerAddrs[dialin.Addr] = append(ServerAddrs[dialin.Addr], Servers[dialin.ServerName]) + } else { + slog.Info( + "Protocol missmatch for same addresses in dilain", + "name", name, + "address", dialin.Addr, + "protocol", dialin.Protocol, + "expected", ServerAddrs[dialin.Addr][0].Protocol, + ) + } + } else { + ServerAddrs[dialin.Addr] = append(ServerAddrs[dialin.Addr], Servers[dialin.ServerName]) + } + } + + slog.Debug( + "Loaded Servers", + "Servers", Servers, + ) + + slog.Debug( + "Loaded ServerAddrs", + "ServerAddrs", ServerAddrs, + ) + + // Prepare listener list + for name, srvlst := range ServerAddrs { + var listenAddr string + + // override server listern address with relayIP if present. + // This is for running mutliple instance of relay in the same + // development host. + if utils.RelayIPFromConfig == "" { + listenAddr = name + } else { + listenAddr = utils.RelayIPFromConfig + } + + srvlistener := &ServerListen{ + Addr: listenAddr, + Protocol: srvlst[0].Protocol, + ServerList: srvlst, + } + + // set the list cert/key paires for this listener + for _, srv := range srvlst { + snicert := utils.SNICertificate{ + CertFile: srv.ServerCRT, + KeyFile: srv.ServerKEY, + } + srvlistener.Certs = append(srvlistener.Certs, snicert) + // set the rootCA list for this listener + srvlistener.RootCAs = append(srvlistener.RootCAs, string(srv.RootCA)) + } + + ServerListeners = append(ServerListeners, srvlistener) + } + + slog.Info( + "Loaded server ", + "num-listeners", len(ServerListeners), + ) +} + +func (srv *Server) connectRequest(key string, msg *utils.ControlMessage, r io.Reader) (*http.Request, error) { + req, err := http.NewRequest(http.MethodPut, srv.DialinPool.URL(key), r) + if err != nil { + return nil, fmt.Errorf("could not create request: %s", err) + } + // write message to header + utils.WriteToHeader(req.Header, msg) + + return req, nil +} + +//dialinLookup check for cluster connection +func dialinCountLookup(sni string) int { + for _, dsrv := range Servers { + if dsrv.Type == utils.DIALIN { + // look dialin pool to find dilain connection key + cnt, err := dsrv.DialinPool.GetDialinConnectorCount(sni) + if err == nil { + return cnt + } + } + } + return 0 +} + +//ProcessPeerForwards ... +func (srv *Server) ProcessPeerForwards(w http.ResponseWriter, r *http.Request, lg *relaylogger.RelayLog, relayIP string, certIssue int64) { + + lg.Debug("ProcessPeerForwards:", relayIP, r.TLS.ServerName) + _, port, err := net.SplitHostPort(relayIP) + if err != nil { + port = "443" + } + + tlscfg, err := ClientTLSConfigFromBytes(srv.ServerCRT, srv.ServerKEY, srv.RootCA, r.TLS.ServerName+":"+port) + if err != nil { + lg.Error( + err, + "Failed to process peer configs", + ) + return + } + //setsup the upgradeaware peer handler + peerHandler, err := proxy.PeerKubeHandler(tlscfg, relayIP) + if err != nil { + lg.Error( + err, + "unable to create peer handler", + ) + return + } + + utils.SetXForwardedFor(r.Header, r.RemoteAddr) + //Update the UUID in the header to detect loops + utils.SetXRAYUUID(r.Header) + + r.Header.Set("X-Rafay-User-Cert-Issued", strconv.FormatInt(certIssue, 10)) + + // set peer security upstream headers + err = utils.PeerSetHeaderNonce(r.Header) + if err != nil { + lg.Error( + err, + "unable to set security headers in peer upstream", + ) + return + } + + //upgradeaware upstreaming + peerHandler.ServeHTTP(w, r) + lg.Debug( + "done Serving peerHandler.ServeHTTP ", + ) +} + +// ProcessCDPeerForwards ... +func (srv *Server) ProcessCDPeerForwards(ctx context.Context, conn net.Conn, lg *relaylogger.RelayLog, relayIP string, state tls.ConnectionState) { + var peerAddr string + lg.Debug("ProcessCDPeerForwards:", relayIP, state.ServerName) + _, port, err := net.SplitHostPort(relayIP) + if err != nil { + port = "443" + peerAddr = relayIP + ":443" + } + + tlscfg, err := ClientTLSConfigFromBytes(srv.ServerCRT, srv.ServerKEY, srv.RootCA, state.ServerName+":"+port) + if err != nil { + lg.Error( + err, + "Failed to process peer configs", + ) + return + } + + pconn, err := tls.Dial("tcp", peerAddr, tlscfg) + if err != nil { + lg.Error( + err, + "failed to connect to peer", + "addr", peerAddr, + ) + return + } + defer pconn.Close() + + utils.Transfer(flushWriter{pconn, lg, pconn}, conn, lg, "client to cdpeer") + +processCDPeerForwardDone: + for { + select { + case <-ctx.Done(): + break processCDPeerForwardDone + + } + } + + // wait 2 sec before closing + time.Sleep(2 * time.Second) + return +} + +//ProcessRelayRequest process user-facing request +func (srv *Server) ProcessRelayRequest(w http.ResponseWriter, r *http.Request, lg *relaylogger.RelayLog) { + var ( + rafayUserName string + sessionKey string + clusterServerName string + clusterID string + dialinSNI string + certIssue int64 + dialinAttempt int + ok bool + session *sessions.UserSession + ) + + srvlog := lg.WithName("RelayRequest") + srvlog.Debug( + "processing relay request", + ) + + // prepare the dilain lookup key + if r.TLS.ServerName == srv.ServerName { + srvlog.Error( + nil, + "Wildcard ServerName is expected", + "ServerName", srv.ServerName, + "expected SNI", "*."+srv.ServerName, + ) + errStirng := "ERROR: Unauthenticated access not allowed. Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "invalid cert in kubeconfig", http.StatusUnauthorized) + return + } + srvlog.Debug("ProcessRelayRequest", "r.TLS.ServerName", r.TLS.ServerName, "srv.ServerName", srv.ServerName) + + strs := strings.Split(r.TLS.ServerName, strings.ReplaceAll(srv.ServerName, "*", "")) + if len(strs) <= 0 { + srvlog.Error( + nil, + "ServeName is not the suffix of SNI", + "ServerName", srv.ServerName, + "SNI", r.TLS.ServerName, + ) + errStirng := "ERROR: Unauthenticated access not allowed. Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "invalid cert in kubeconfig", http.StatusUnauthorized) + return + } + + if len(r.TLS.PeerCertificates) <= 0 { + srvlog.Error( + nil, + "did not find any peer certificates in the request", + ) + errStirng := "ERROR: Unauthenticated access not allowed. Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "invalid cert in kubeconfig", http.StatusUnauthorized) + return + } + + if r.TLS.PeerCertificates[0].Subject.CommonName == "" { + srvlog.Error( + nil, + "cerficate CN is empty", + ) + errStirng := "ERROR: Unauthenticated access not allowed. Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "invalid cert in kubeconfig", http.StatusUnauthorized) + return + } + + issueDate := r.TLS.PeerCertificates[0].NotBefore + certIssue = issueDate.Unix() + srvlog.Debug("certificate usse epoch in secs", certIssue) + + sessionKey = "" + session = nil + //check the request is from a peer relay + if r.Header.Get("X-Rafay-XRAY-RELAYUUID") == "" { + srvlog.Debug( + "procesing user request common name from cert", + "CN", r.TLS.PeerCertificates[0].Subject.CommonName, + ) + + //User name is extracted from client certificate CN + rafayUserName = r.TLS.PeerCertificates[0].Subject.CommonName + sessionKey = r.TLS.ServerName + ":" + rafayUserName + session, ok = sessions.GetUserSession(sessionKey) + if !ok { + //Create session for the user + session = &sessions.UserSession{ + Type: srv.Type, + ServerName: srv.ServerName, + CertSNI: r.TLS.ServerName, + DialinCachedKey: "", + ErrorFlag: false, + } + sessions.AddUserSession(session, sessionKey) + srvlog.Info( + "Created new session", + "key", sessionKey, + ) + } else { + srvlog.Debug( + "Found existing session", + "key", sessionKey, + "dialin sticky key", session.DialinCachedKey, + ) + } + + clusterID = strs[0] + dialinSNI = strings.ReplaceAll(srv.DialinServerName, "*", strs[0]) + dialinAttempt = 0 + } else { + var err error + // to protect from a bad actor injecting rogue headers we need + // to validate the request tls parametes to make sure the client cert + // has expcted values. + + // 1. using a shared 256 key aes encryption blob added with a nonce + // validate the decrypted text using shared key produces expected result + // to make sure no tampering in headers + /* + if utils.CheckRelayLoops(r.Header) { + var allIds string + + if uuidHdr, ok := r.Header["X-Rafay-XRAY-RELAYUUID"]; ok { + allIds = strings.Join(uuidHdr, ", ") + } + srvlog.Error( + fmt.Errorf("LOOP detected in peerforwards"), + "failed peer loop detection check RelayUUID", utils.RelayUUID, + "header X-Rafay-XRAY-RELAYUUID allIds", allIds, + ) + } + */ + if !utils.CheckPeerHeaders(r.Header) { + srvlog.Error( + fmt.Errorf("failed to validate the peer upstream security header"), + "failed peer upstream due to secuity reasons", + ) + errStirng := "ERROR: Unauthenticated access not allowed . Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "failed to validate request in peer proxy", http.StatusUnauthorized) + return + } + + rafayUserName = r.Header.Get("X-Rafay-User") + clusterServerName = r.Header.Get("X-Rafay-Cluster-ServerName") + clusterID = r.Header.Get("X-Rafay-Cluster-ID") + issDateSecStr := r.Header.Get("X-Rafay-User-Cert-Issued") + if issDateSecStr == "" { + srvlog.Error( + fmt.Errorf("peer did not send a valid user cert issue date header"), + "failed peer upstream due to missing cert issuedate header", + ) + } + certIssue, err = strconv.ParseInt(issDateSecStr, 10, 64) + if err != nil { + srvlog.Error( + fmt.Errorf("peer did not send a valid user cert issue date header"), + "failed peer upstream due to missing cert issuedate header", + ) + } + + srvlog.Debug( + "forwarded request from peer", + "rafayUserName", rafayUserName, + "clusterServerName", clusterServerName, + "clusterID", clusterID, + "certIssue:", certIssue, + ) + + if rafayUserName == "" || clusterServerName == "" || clusterID == "" { + srvlog.Error( + nil, + "Did not find required headers", + "X-Rafay-User", rafayUserName, + "X-Rafay-Cluster-Name", clusterServerName, + "X-Rafay-Cluster-ID", clusterID, + ) + errStirng := "ERROR: Unauthenticated access not allowed (kubeconfig invalid cert). Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "failed to find user/cluster details", http.StatusUnauthorized) + return + } + + sessionKey = clusterServerName + ":" + rafayUserName + session, ok = sessions.GetUserSession(sessionKey) + if !ok { + //Create session for the user + session = &sessions.UserSession{ + Type: srv.Type, + ServerName: srv.ServerName, + CertSNI: clusterServerName, + DialinCachedKey: "", + ErrorFlag: false, + } + sessions.AddUserSession(session, sessionKey) + srvlog.Info( + "Created new session", + "key", sessionKey, + ) + } else { + srvlog.Debug( + "Found existing session", + "key", sessionKey, + "dialin sticky key", session.DialinCachedKey, + ) + } + + //prifixName is the clusterid/uuid for the endpoint + dialinSNI = strings.ReplaceAll(srv.DialinServerName, "*", clusterID) + dialinAttempt = 0 + } + r.Header.Set("X-Rafay-Audit", "yes") + +retryDialin: + // get the dialin server instance + if dsrv, ok := Servers[srv.DialinServerName]; ok { + if session.DialinCachedKey == "" { + // look dialin pool to find dilain connection key + key, err := dsrv.DialinPool.GetDialinConnectorKey(dialinSNI) + if err != nil { + srvlog.Info( + "No dialins", + "error", err, + "dialinAttempt", dialinAttempt, + ) + // Lookup peerCache to fetch cluster connection + // On cache-miss send the probe, wait and retry 5 times + relayIP, found := peerclient.GetPeerCache(utils.PeerCache, dialinSNI) + if found { + // forward the client request to peer upstream + r.Header.Set("X-Rafay-User", rafayUserName) + r.Header.Set("X-Rafay-Cluster-ServerName", r.TLS.ServerName) + r.Header.Set("X-Rafay-Cluster-ID", clusterID) + r.Header.Del("X-Rafay-Audit") + srv.ProcessPeerForwards(w, r, lg, relayIP, certIssue) + return + } else { + if dialinAttempt == 0 { + go SendPeerProbe(PeerProbeChanel, dialinSNI) + } + + dialinAttempt++ + if dialinAttempt > 7 { + errStirng := "ERROR: failed to forward request to cluster. Please retry" + jsonError(w, errStirng, "failed to find connection", http.StatusInternalServerError) + return + } + // wait for total ~20 sec to learn from core + // else drop the connection + time.Sleep(3 * time.Second) + goto retryDialin + } + } else { + //cache it + session.DialinCachedKey = key + } + } else { + //verify cache is valid + if !dsrv.DialinPool.CheckDialinKeyExist(session.DialinCachedKey) { + session.DialinCachedKey = "" + goto retryDialin + } + } + + srvlog.Debug( + "dialin connection lookup", + "key", session.DialinCachedKey, + "sessionKey", sessionKey, + ) + + //unix socket for stiching user request to dialin + socketPath := utils.UNIXSOCKET + srv.DialinServerName + + if session.UserName == "" { + userName, roleName, isRead, isOrgAdmin, enforceOrgAdminSecret, err := srv.Provisioner.ProvisionAuthzForUser(socketPath, rafayUserName, r.TLS.ServerName, session.DialinCachedKey, session.ErrorFlag, false, certIssue) + if err != nil { + var errStirng string + srvlog.Error( + err, + "unable to provision authz for user", + ) + if userName == "" { + errStirng = "ERROR: Unauthenticated access not allowed. Please log in to the portal via browser, or set up API key, for access via the secure kubectl proxy. Error:" + err.Error() + } else { + errStirng = "ERROR: Connection timed-out. Unable to provision cluster RBAC. Please retry." + } + jsonError(w, errStirng, "unable to proxy kubectl service", http.StatusUnauthorized) + session.DialinCachedKey = "" + sessions.DeleteUserSession(sessionKey) + return + } + session.UserName = userName + session.IsReadrole = isRead + session.RoleName = roleName + session.IsOrgAdmin = isOrgAdmin + session.EnforceOrgAdminOnlySecret = enforceOrgAdminSecret + } else { + go func() { + _, roleName, isRead, isOrgAdmin, enforceOrgAdminSecret, err := srv.Provisioner.ProvisionAuthzForUser(socketPath, rafayUserName, r.TLS.ServerName, session.DialinCachedKey, session.ErrorFlag, true, certIssue) + if err != nil { + srvlog.Error( + err, + "unable to provision authz for user", + ) + session.UserName = "" + } + session.IsReadrole = isRead + session.RoleName = roleName + session.IsOrgAdmin = isOrgAdmin + session.EnforceOrgAdminOnlySecret = enforceOrgAdminSecret + }() + } + + //check readrole vs pod exec + if session.IsReadrole { + if sessions.GetRoleCheck(r.Method, r.URL.Path) { + errStirng := fmt.Sprintf("ERROR: request forbidden for the role %s.", session.RoleName) + jsonError(w, errStirng, "unable to authorize user request", http.StatusUnauthorized) + return + } + } + + if session.EnforceOrgAdminOnlySecret && !session.IsOrgAdmin { + if sessions.GetSecretRoleCheck(r.Method, r.URL.Path) { + errStirng := "ERROR: request for secret resource is forbidden. Contact your organization admin" + jsonError(w, errStirng, "unable to authorize user request to secret", http.StatusUnauthorized) + return + } + } + + //setsup the upgradeaware unix handler + unixHandler, err := proxy.UnixKubeHandler(socketPath, session.DialinCachedKey, rafayUserName, r.TLS.ServerName) + if err != nil { + srvlog.Error( + err, + "unable to create unix handler", + ) + errStirng := "ERROR: failed to forward request to cluster. Please retry" + jsonError(w, errStirng, "unable to create forward handler", http.StatusInternalServerError) + return + } + + //These are the headers used by relay-agent to fetch + //user information. + r.Header.Set("X-Rafay-User", session.UserName) + r.Header.Set("X-Rafay-Key", session.DialinCachedKey) + r.Header.Set("X-Rafay-Namespace", "rafay-system") + r.Header.Set("X-Rafay-Sessionkey", sessionKey) + utils.SetXForwardedFor(r.Header, r.RemoteAddr) + utils.SetXRAYUUID(r.Header) + if session.ErrorFlag { + r.Header.Set(utils.HeaderClearSecret, "true") + } + + //upgradeaware uinix upstreaming + unixHandler.ServeHTTP(w, r) + srvlog.Debug( + "done Serving unixHandler.ServeHTTP ", + ) + if session.ErrorFlag { + session.DialinCachedKey = "" + sessions.DeleteUserSession(sessionKey) + } + } +} + +func jsonError(w http.ResponseWriter, message, reason string, code int) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(code) + resp := &kubeError{Kind: "Status", APIVersion: "v1", Metadata: nil, Status: "Failure", Message: message, Reason: reason, Code: code} + e, err := json.Marshal(resp) + if err == nil { + fmt.Fprintln(w, string(e)) + return + } + fmt.Fprintln(w, message+" "+reason) +} + +//ServeHTTP requests from userfacing connection +func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + servelog := slog.WithName("RelayServeHTTP") + servelog.Debug( + "start serving user http", + ) + + if len(r.TLS.PeerCertificates) <= 0 { + servelog.Error( + nil, + "no client certificate in request", + ) + errStirng := "ERROR: Unauthenticated access not allowed. Please log in to the portal and download new kubeconfig" + jsonError(w, errStirng, "invalid cert in kubeconfig", http.StatusUnauthorized) + return + } + + //client certificate + servelog.Debug( + "describe client certificate", + "ServerName", r.TLS.ServerName, + "Peer CN", r.TLS.PeerCertificates[0].Subject.CommonName, + "Peer Serial", r.TLS.PeerCertificates[0].Subject.SerialNumber, + "NegotiatedProtocol", r.TLS.NegotiatedProtocol, + "NegotiatedProtocolIsMutual", r.TLS.NegotiatedProtocolIsMutual, + ) + + switch srv.Type { + case utils.RELAY: + srv.ProcessRelayRequest(w, r, servelog) + default: + servelog.Error( + nil, + "unexpected server type", + "ServerName", srv.ServerName, + "type", srv.Type, + ) + } + +} + +//AddToDialinPool add connection to dialin pool of the server +func (srv *Server) AddToDialinPool(rconn *RelayConn, remoteAddr string) (string, error) { + slog.Info( + "AddToDialinPool", + "identifier", rconn.PeerID, + "ServerName", rconn.ServerName, + "CertSNI", rconn.CertSNI, + ) + + key, err := srv.DialinPool.AddConn(rconn.Conn, rconn.PeerID, rconn.CertSNI, remoteAddr) + if err != nil { + slog.Error( + err, + "adding connection failed", + "server", srv.ServerName, + ) + return "", err + } + + slog.Info( + "Added conn to pool of", + "server", srv.ServerName, + ) + + return key, nil +} + +func getSNIMuxDebugString(tmp []byte) string { + dstr := "" + for i := range tmp { + if i >= len(tmp) { + break + } + b := tmp[i] + if b < 32 || b > 126 { + dstr = dstr + " . " + } else { + dstr = dstr + " " + string(b) + } + } + return dstr +} + +//StartHTTPSListen start TLS listen on address +//Both user & dialin endpoint listen on 443 +//Based on SNI traffic is routed/muxed to appropriate handler +func (sl *ServerListen) StartHTTPSListen(ctx context.Context) { + slistenlog := slog.WithName("Listener") + slistenlog.Info( + "starting listener", + "addr", sl.Addr, + "protocol", sl.Protocol, + "num-servers", len(sl.ServerList), + ) + + l, err := net.Listen("tcp", sl.Addr) + if err != nil { + slistenlog.Error( + err, + "failed to listen", + "addr", sl.Addr, + ) + return + } + + //close the listener when ctx is done + go func() { + defer l.Close() + + <-ctx.Done() + slistenlog.Info( + "stopping listener", + "address", sl.Addr, + ) + }() + + sl.Mux, err = vhost.NewTLSMuxer(l, utils.DefaultMuxTimeout) + if err != nil { + slistenlog.Error( + err, + "failed in NewTLSMuxer", + ) + return + } + + for name, srv := range Servers { + slistenlog.Info( + "start mux listen on ", + "sni", name, + ) + snil, err := sl.Mux.Listen(name) + if err != nil { + slistenlog.Error( + err, + "failed in sl.Mux.Listen", + "sni", name, + ) + return + } + //listen routine for sni (name) + go srv.sniListen(ctx, name, snil, sl) + + if srv.Type == utils.DIALIN && utils.Mode != utils.CDRELAY { + //start the unix socket listen for each + //dialin server block + go srv.startUnixListen(ctx, slistenlog) + } + } + + // custom error handler for the mux + go func() { + for { + conn, err := sl.Mux.NextError() + vhostName := "" + tlsConn, ok := conn.(*vhost.TLSConn) + dbgStr := "" + if conn != nil { + tmp := make([]byte, 256) + n, _ := conn.Read(tmp) + if n > 0 { + dbgStr = getSNIMuxDebugString(tmp) + } + } + + if ok { + vhostName = tlsConn.Host() + slistenlog.Error( + nil, + "vhostName", vhostName, + "raddr", conn.RemoteAddr(), + "laddr", conn.LocalAddr(), + "peekbuf", dbgStr, + ) + } else { + slistenlog.Error( + nil, + "error in getting tlsConn", + "raddr", conn.RemoteAddr(), + "laddr", conn.LocalAddr(), + "peekbuf", dbgStr, + ) + } + + switch err.(type) { + case vhost.BadRequest: + slistenlog.Info( + "got a bad request!", + "addr", conn.RemoteAddr(), + "tlsConn", tlsConn, + "err", err, + ) + case vhost.NotFound: + slistenlog.Error( + err, + "got a connection for an unknown vhost", + "addr", vhostName, + "tlsConn", tlsConn, + ) + case vhost.Closed: + slistenlog.Error( + err, + "vhost closed conn", + "addr", vhostName, + "tlsConn", tlsConn, + ) + } + + if conn != nil { + slistenlog.Debug( + "closing connection", + "raddr", conn.RemoteAddr(), + "laddr", conn.LocalAddr(), + ) + conn.Close() + } + } + }() +} + +func (srv *Server) proxyUnixConnection(ctx context.Context, conn net.Conn, lg *relaylogger.RelayLog) { + var pmsg utils.ProxyProtocolMessage + + defer conn.Close() + conn.SetReadDeadline(time.Now().Add(utils.DefaultTimeout)) + //first utils.ProxyProtocolSize bytes reserved for proxy protocol like buffer + buf := make([]byte, utils.ProxyProtocolSize) + n, err := io.ReadAtLeast(conn, buf, utils.ProxyProtocolSize) + if err != nil { + lg.Error( + err, + "failed in unix listen to read", + "size", utils.ProxyProtocolSize, + "srv", srv.ServerName, + ) + } + + lg.Debug( + "unix listen read proxy protocol info", + "msglen", n, + "message", string(buf), + ) + + if n == utils.ProxyProtocolSize { + n = bytes.IndexByte(buf, 0) + err = json.Unmarshal(buf[:n], &pmsg) + if err != nil { + lg.Error( + err, + "failed to parse json", + "msg", string(buf[:n]), + ) + return + } + } else { + return + } + + if !srv.DialinPool.CheckDialinKeyExist(pmsg.DialinKey) { + lg.Error( + nil, + "did not find key in the dilain pool", + "key", pmsg.DialinKey, + ) + return + } + + pr, pw := io.Pipe() + defer pr.Close() + defer pw.Close() + + msg := &utils.ControlMessage{ + Action: utils.ActionProxy, + ForwardedHost: pmsg.SNI, + ForwardedService: srv.Name, + RafayUserName: pmsg.UserName, // for testing only + RafayNamespace: "default", // for testing only + RafayScope: "default", // for testing only + RafayAllow: "true", // for testing only + } + + req, err := srv.connectRequest(pmsg.DialinKey, msg, pr) + if err != nil { + lg.Error( + err, + "failed in srv.connectRequest", + ) + return + } + + nctx, cancel := context.WithCancel(ctx) + req = req.WithContext(nctx) + + done := make(chan struct{}) + go func() { + utils.Transfer(flushWriter{pw, lg, conn}, conn, lg, "unix to tunnel") + cancel() + close(done) + }() + + resp, err := srv.httpClient.Do(req) + if err != nil { + lg.Error( + err, + "io error:", + ) + return + } + defer resp.Body.Close() + + utils.Transfer(flushWriter{conn, lg, conn}, resp.Body, lg, "tunnel to unix") + +proxyUnixConnectionDone: + for { + select { + case <-nctx.Done(): + break proxyUnixConnectionDone + case <-ctx.Done(): + break proxyUnixConnectionDone + case <-done: + break proxyUnixConnectionDone + case <-time.After(300 * time.Second): + lg.Error( + fmt.Errorf("proxyUnixConnection waited 5 min for idle client to close"), + "break the loop to avoid memory exhaustion", + ) + break proxyUnixConnectionDone + } + } + + // wait 2 sec before closing + time.Sleep(2 * time.Second) +} + +func (srv *Server) proxyCDDilainConnection(ctx context.Context, conn net.Conn, dialinKey, rafayUserName, sni string, lg *relaylogger.RelayLog) { + defer conn.Close() + conn.SetReadDeadline(time.Now().Add(utils.DefaultTimeout)) + + pr, pw := io.Pipe() + defer pr.Close() + defer pw.Close() + + msg := &utils.ControlMessage{ + Action: utils.ActionProxy, + ForwardedHost: sni, + ForwardedService: srv.ServerName, + RafayUserName: rafayUserName, // for testing only + RafayNamespace: "default", // for testing only + RafayScope: "default", // for testing only + RafayAllow: "true", // for testing only + } + + req, err := srv.connectRequest(dialinKey, msg, pr) + if err != nil { + lg.Error( + err, + "failed in srv.connectRequest", + ) + return + } + + nctx, cancel := context.WithCancel(ctx) + req = req.WithContext(nctx) + + done := make(chan struct{}) + go func() { + utils.Transfer(flushWriter{pw, lg, conn}, conn, lg, "client to tunnel") + cancel() + close(done) + }() + + resp, err := srv.httpClient.Do(req) + if err != nil { + lg.Error( + err, + "io error:", + ) + return + } + defer resp.Body.Close() + + utils.Transfer(flushWriter{conn, lg, conn}, resp.Body, lg, "tunnel to client") + +proxyCDDilainConnectionDone: + for { + select { + case <-nctx.Done(): + break proxyCDDilainConnectionDone + case <-ctx.Done(): + break proxyCDDilainConnectionDone + case <-done: + break proxyCDDilainConnectionDone + case <-time.After(300 * time.Second): + lg.Error( + fmt.Errorf("proxyCDDilainConnection waited 5 min for idle client to close"), + "break the loop to avoid memory exhaustion", + ) + break proxyCDDilainConnectionDone + } + } + + // wait 2 sec before closing + time.Sleep(2 * time.Second) +} + +//start the unix socket that listens for connections to +//stich to a dialin. User request are handled here +func (srv *Server) startUnixListen(ctx context.Context, lg *relaylogger.RelayLog) { + //start listen the dialin unix pipe + socketpath := utils.UNIXSOCKET + srv.ServerName + syscall.Unlink(socketpath) + ul, err := net.Listen("unix", socketpath) + if err != nil { + lg.Error( + err, + "couldn't listen to", + "socketpath", socketpath, + ) + } + + //close the listener when ctx is done + go func() { + defer ul.Close() + + <-ctx.Done() + lg.Info( + "stopping listener", + "address", socketpath, + ) + }() + + lg.Info( + "started unix listen", + "socketpath", socketpath, + ) + + uctx, cancel := context.WithCancel(ctx) + defer cancel() + // Wait for a connection and accept it + for { + conn, err := ul.Accept() + + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") || + strings.Contains(err.Error(), "Listener closed") { + lg.Error( + err, + "unix listener closed", + "srv", srv.ServerName, + ) + return + } + lg.Error( + err, + "Accept failed ", + "srv", srv.ServerName, + ) + continue + } + go srv.proxyUnixConnection(uctx, conn, lg) + } +} + +//sni based muxing (virtual hosting) handler that get called based on SNI +func (srv *Server) sniListen(ctx context.Context, sni string, l net.Listener, sl *ServerListen) { + var tlsconfig *tls.Config + var err error + + snilistenlog := slog.WithName("SNIListen") + + // prepare TLS configuration for the server + if srv.Type == utils.RELAY { + tlsconfig, err = ServerTLSConfigFromBytes(sl.Certs, sl.RootCAs, "http/1.1") + } else if srv.Type == utils.CDRELAY { + tlsconfig, err = ServerTLSConfigFromBytes(sl.Certs, sl.RootCAs) + } else if srv.Type == utils.DIALIN { + tlsconfig, err = ServerTLSConfigFromBytes(sl.Certs, sl.RootCAs) + } else { + snilistenlog.Error(nil, "unknown server type ", srv.Type, "addr", sl.Addr) + return + } + + if err != nil { + snilistenlog.Error( + err, + "failed to create tlsconfig", + "addr", sl.Addr, + ) + return + } + + if srv.Type == utils.RELAY { + //User facing server + srv.httpServer.TLSConfig = tlsconfig + srv.httpServer.Handler = http.HandlerFunc(srv.ServeHTTP) + + snilistenlog.Debug( + "starting srv.httpServer.ServeTLS", + "sni", sni, + "name", srv.ServerName, + "addr", sl.Addr, + ) + + srv.httpServer.Handler = audit.WrapWithAudit( + srv.httpServer.Handler, + audit.WithBasePath(srv.auditPath), + audit.WithMaxSizeMB(1), + audit.WithMaxBackups(20), + audit.WithMaxAgeDays(10), + ) + + //start TLS handshake + srv.httpServer.ServeTLS(l, "", "") + } + + if srv.Type == utils.CDRELAY { + for { + // accept connections + conn, err := l.Accept() + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + snilistenlog.Error( + err, + "control connection listener closed", + "addr", sl.Addr, + ) + return + } + + snilistenlog.Error( + err, + "accept of connection failed", + "addr", sl.Addr, + ) + continue + } + + // handler for the accepted CD connections + go srv.handleCDRelayConnection(ctx, conn, tlsconfig) + } + } + + if srv.Type == utils.DIALIN { + for { + // accept connections + conn, err := l.Accept() + if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + snilistenlog.Error( + err, + "control connection listener closed", + "addr", sl.Addr, + ) + return + } + + snilistenlog.Error( + err, + "accept of connection failed", + "addr", sl.Addr, + ) + continue + } + + // handler for the accepted connection + go srv.handleDialinConnection(conn, tlsconfig) + } + } + + snilistenlog.Error( + nil, + "unknown service type ", + "type", srv.Type, + ) +} + +func (srv *Server) sendDialinHandshake(key string, lg *relaylogger.RelayLog) error { + var hMsg handShakeMsg + + sndlog := lg.WithName("sendDialinHandshake") + + sndlog.Debug( + "send dialin handhshake", + "key", key, + "httpClient.Transport", srv.httpClient.Transport, + "pool", srv.DialinPool, + ) + + req, err := http.NewRequest(http.MethodConnect, srv.DialinPool.URL(key), nil) + if err != nil { + sndlog.Error( + err, + "handshake request creation failed", + ) + return err + } + + { + ctx, cancel := context.WithTimeout(context.Background(), utils.DefaultTimeout) + defer cancel() + req = req.WithContext(ctx) + } + + resp, err := srv.httpClient.Do(req) + if err != nil { + sndlog.Error( + err, + "handshake failed", + ) + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("Status %s", resp.Status) + sndlog.Error( + err, + "dialin handshake response is not 200 OK", + "resp.StatusCode", resp.StatusCode, + ) + return err + } + + if resp.ContentLength == 0 { + err = fmt.Errorf("Tunnels Content-Legth: 0") + sndlog.Error( + err, + "dialin handshake response has 0 content length", + ) + return err + } + + if err = json.NewDecoder(&io.LimitedReader{R: resp.Body, N: 126976}).Decode(&hMsg); err != nil { + sndlog.Error( + err, + "dialin handshake failed to parse json", + ) + return err + } + + sndlog.Info( + "recieved handshake message", + "hMsg", hMsg, + ) + + return nil +} + +func (srv *Server) processCDRelayTLSState(ctx context.Context, conn net.Conn, lg *relaylogger.RelayLog, tlsconfig *tls.Config) (*Server, error) { + var ( + rafayUserName string + sessionKey string + dialinSNI string + dialinAttempt int + ok bool + session *sessions.UserSession + ) + + tlsConn, ok := conn.(*tls.Conn) + if !ok { + slog.Error( + nil, + "invalid connection type", + "err", fmt.Errorf("expected TLS conn, got %T", conn), + ) + return nil, fmt.Errorf("expected TLS conn, got %T", conn) + } + + // perform TLS handshake + if err := tlsConn.Handshake(); err != nil { + slog.Error( + err, + "failed to compete TLS handshake", + ) + return nil, fmt.Errorf("ailed to compete TLS handshake") + } + + if err := conn.SetDeadline(time.Time{}); err != nil { + slog.Error( + err, + "failed to SetDeadline", + ) + return nil, fmt.Errorf("failed to SetDeadline") + } + + state := tlsConn.ConnectionState() + + _, err := GetRemoteCertID(tlsConn) + if err != nil { + slog.Error( + err, + "error in getting client cert id", + ) + return nil, fmt.Errorf("error in getting client cert id") + } + + // Handshake completed + // prepare the dilain lookup key + if state.ServerName == srv.ServerName { + slog.Error( + nil, + "Wildcard ServerName is expected", + "ServerName", srv.ServerName, + "expected SNI", "*."+srv.ServerName, + ) + return nil, fmt.Errorf("ERROR: Unauthenticated access not allowed") + } + + slog.Debug("processCDRelayTLSState", "ServerName", state.ServerName, "srv.ServerName", srv.ServerName) + + strs := strings.Split(state.ServerName, strings.ReplaceAll(srv.ServerName, "*", "")) + if len(strs) <= 0 { + slog.Error( + nil, + "ServeName is not the suffix of SNI", + "ServerName", srv.ServerName, + "SNI", state.ServerName, + ) + return nil, fmt.Errorf("ERROR: Unauthenticated access not allowed") + } + + if len(state.PeerCertificates) <= 0 { + slog.Error( + nil, + "did not find any peer certificates in the request", + ) + return nil, fmt.Errorf("ERROR: Unauthenticated access not allowed") + } + + if state.PeerCertificates[0].Subject.CommonName == "" { + slog.Error( + nil, + "cerficate CN is empty", + ) + return nil, fmt.Errorf("ERROR: Unauthenticated access not allowed") + } + + sessionKey = "" + session = nil + rafayUserName = state.PeerCertificates[0].Subject.CommonName + sessionKey = state.ServerName + ":" + rafayUserName + session, ok = sessions.GetUserSession(sessionKey) + if !ok { + //Create session for the user + session = &sessions.UserSession{ + Type: srv.Type, + ServerName: srv.ServerName, + CertSNI: state.ServerName, + DialinCachedKey: "", + ErrorFlag: false, + } + sessions.AddUserSession(session, sessionKey) + slog.Info( + "Created new session", + "key", sessionKey, + ) + } else { + slog.Debug( + "Found existing session", + "key", sessionKey, + "dialin sticky key", session.DialinCachedKey, + ) + } + + clusterID := strs[0] + dialinSNI = strings.ReplaceAll(srv.DialinServerName, "*", clusterID) + dialinAttempt = 0 + +retryCDDialin: + // get the dialin server instance + if dsrv, ok := Servers[srv.DialinServerName]; ok { + if session.DialinCachedKey == "" { + // look dialin pool to find dilain connection key + key, err := dsrv.DialinPool.GetDialinConnectorKey(dialinSNI) + if err != nil { + slog.Info( + "No dialins", + "error", err, + "dialinAttempt", dialinAttempt, + ) + // Lookup peerCache to fetch cluster connection + // On cache-miss send the probe, wait and retry 5 times + relayIP, found := peerclient.GetPeerCache(utils.PeerCache, dialinSNI) + if found { + // forward the client request to peer upstream + srv.ProcessCDPeerForwards(ctx, conn, lg, relayIP, state) + return srv, nil + } + if dialinAttempt == 0 { + go SendPeerProbe(PeerProbeChanel, dialinSNI) + } + + dialinAttempt++ + if dialinAttempt > 7 { + return nil, fmt.Errorf("ERROR: no dialns in both local and peers") + } + // wait for total ~20 sec to learn from core + // else drop the connection + time.Sleep(3 * time.Second) + goto retryCDDialin + + } else { + //cache it + session.DialinCachedKey = key + } + } else { + //verify cache is valid + if !dsrv.DialinPool.CheckDialinKeyExist(session.DialinCachedKey) { + session.DialinCachedKey = "" + goto retryCDDialin + } + } + + slog.Info( + "dialin connection lookup", + "key", session.DialinCachedKey, + "sessionKey", sessionKey, + ) + + session.UserName = rafayUserName + + //dilain proxy + dsrv.proxyCDDilainConnection(ctx, conn, session.DialinCachedKey, rafayUserName, state.ServerName, slog) + } else { + slog.Info( + "did not find dialin server name ", + "srv.DialinServerName", srv.DialinServerName, + ) + } + + return srv, nil +} + +func (srv *Server) processDialinTLSState(conn net.Conn, lg *relaylogger.RelayLog, tlsconfig *tls.Config) (*Server, error) { + tlsConn, ok := conn.(*tls.Conn) + if !ok { + slog.Error( + nil, + "invalid connection type", + "err", fmt.Errorf("expected TLS conn, got %T", conn), + ) + return nil, fmt.Errorf("expected TLS conn, got %T", conn) + } + + // perform TLS handshake + if err := tlsConn.Handshake(); err != nil { + slog.Error( + err, + "failed to compete TLS handshake", + ) + return nil, fmt.Errorf("ailed to compete TLS handshake") + } + + if err := conn.SetDeadline(time.Time{}); err != nil { + slog.Error( + err, + "failed to SetDeadline", + ) + return nil, fmt.Errorf("failed to SetDeadline") + } + + // Handshake completed, add to Connections map; key is remoteAddr + if !isConnectionExist(conn.RemoteAddr().String()) { + var rconn *RelayConn + state := tlsConn.ConnectionState() + + identifier, err := GetRemoteCertID(tlsConn) + if err != nil { + slog.Error( + err, + "error in getting client cert id", + ) + return nil, fmt.Errorf("error in getting client cert id") + } + + rconn = &RelayConn{ + Conn: conn, + Type: srv.Type, + ServerName: srv.ServerName, + CertSNI: state.ServerName, + PeerID: identifier, + server: srv, + } + + addConnection(conn.RemoteAddr().String(), rconn) + + if rconn.Type == utils.DIALIN { + // add to dialin pool + key, err := srv.AddToDialinPool(rconn, conn.RemoteAddr().String()) + if err != nil { + deleteConnections(conn.RemoteAddr().String()) + return nil, err + } + // send init handshake + err = srv.sendDialinHandshake(key, slog) + if err != nil { + // handshake failed; delete added connection + srv.DialinPool.DeleteConn(rconn.PeerID, rconn.CertSNI, conn.RemoteAddr().String()) + deleteConnections(conn.RemoteAddr().String()) + return nil, fmt.Errorf("failed to send dialin handshake") + } + } else { + return nil, fmt.Errorf("unknown service type") + } + + return srv, nil + } + + return nil, fmt.Errorf("connection already exist %s", conn.RemoteAddr().String()) + +} + +func (srv *Server) handleCDRelayConnection(ctx context.Context, conn net.Conn, tlsconfig *tls.Config) { + defer conn.Close() + var vhostName string + hlog := slog.WithName("handleDialinConnection") + + vhostName = "" + tlsVConn, ok := conn.(*vhost.TLSConn) + if ok { + vhostName = tlsVConn.Host() + } + + if vhostName == "" { + slog.Error( + nil, + "vhost name is empty", + ) + return + } + + // prcess the TLS layer + _, err := srv.processCDRelayTLSState(ctx, tls.Server(conn, tlsconfig), hlog, tlsconfig) + if err != nil { + hlog.Error( + err, + "failed to process TLS state", + ) + return + } + +} + +func (srv *Server) handleDialinConnection(conn net.Conn, tlsconfig *tls.Config) { + var vhostName string + hlog := slog.WithName("handleDialinConnection") + + vhostName = "" + tlsVConn, ok := conn.(*vhost.TLSConn) + if ok { + vhostName = tlsVConn.Host() + } + + if vhostName == "" { + slog.Error( + nil, + "vhost name is empty", + ) + conn.Close() + return + } + + // prcess the TLS layer + _, err := srv.processDialinTLSState(tls.Server(conn, tlsconfig), hlog, tlsconfig) + if err != nil { + hlog.Error( + err, + "failed to process TLS state", + ) + conn.Close() + return + } + +} + +func cleanServerName(s string) string { + return strings.ReplaceAll(s, "*", "star") +} + +// StartServer starts server +func StartServer(ctx context.Context, log *relaylogger.RelayLog, auditPath string, exitChan chan<- bool) { + var config *ServerConfig + var err error + slog = log.WithName("Server") + + config = &ServerConfig{} + config.Relays = make(map[string]*Relay) + config.Relays["kubectl"] = &Relay{ + Protocol: "https", + Addr: fmt.Sprintf(":%d", utils.RelayUserPort), + ServerName: utils.RelayUserHost, + DialinSfx: utils.RelayConnectorHost, + RootCA: utils.RelayUserCACert, + ServerCRT: utils.RelayUserCert, + ServerKEY: utils.RelayUserKey, + } + config.Dialins = make(map[string]*Dialin) + config.Dialins["kubectl"] = &Dialin{ + Protocol: "https", + Addr: fmt.Sprintf(":%d", utils.RelayConnectorPort), + ServerName: utils.RelayConnectorHost, + RootCA: utils.RelayConnectorCACert, + ServerCRT: utils.RelayConnectorCert, + ServerKEY: utils.RelayConnectorKey, + } + config.AuditPath = auditPath + + utils.PeerCache, err = peerclient.InitPeerCache(nil) + if err != nil { + slog.Error( + err, + "failed to init peer cache", + ) + return + } + + loadRelayServers(config) + + err = sessions.InitUserSessionCache() + if err != nil { + slog.Error( + err, + "failed to init user session cache", + ) + return + } + + if err := proxy.InitUnixCacheRoundTripper(); err != nil { + slog.Error( + err, + "failed to init unix cached round tripper", + ) + return + } + + if err := proxy.InitPeerCacheRoundTripper(); err != nil { + slog.Error( + err, + "failed to init unix cached round tripper", + ) + return + } + + // Start listeners + for _, listener := range ServerListeners { + switch listener.Protocol { + case utils.HTTPS: + go listener.StartHTTPSListen(ctx) + default: + slog.Error( + nil, + "unknown protocol", + "listener.Protocol", listener.Protocol, + "Addr", listener.Addr, + ) + } + } + + go StartPeeringMgr(ctx, log, exitChan, config) + + StartDialinPoolMgr(ctx, log, exitChan) + + for { + select { + case <-ctx.Done(): + slog.Error( + ctx.Err(), + "stoping Server", + ) + return + } + } +} + +// StartCDServer starts server +func StartCDServer(ctx context.Context, log *relaylogger.RelayLog, auditPath string, exitChan chan<- bool) { + var config *ServerConfig + var err error + slog = log.WithName("Server") + + config = &ServerConfig{} + config.CDRelays = make(map[string]*Relay) + config.CDRelays["cdrelay"] = &Relay{ + Protocol: "https", + Addr: fmt.Sprintf(":%d", utils.CDRelayUserPort), + ServerName: utils.CDRelayUserHost, + DialinSfx: utils.CDRelayConnectorHost, + RootCA: utils.CDRelayUserCACert, + ServerCRT: utils.CDRelayUserCert, + ServerKEY: utils.CDRelayUserKey, + } + config.Dialins = make(map[string]*Dialin) + config.Dialins["cdrelay"] = &Dialin{ + Protocol: "https", + Addr: fmt.Sprintf(":%d", utils.CDRelayConnectorPort), + ServerName: utils.CDRelayConnectorHost, + RootCA: utils.CDRelayConnectorCACert, + ServerCRT: utils.CDRelayConnectorCert, + ServerKEY: utils.CDRelayConnectorKey, + } + config.AuditPath = auditPath + + utils.PeerCache, err = peerclient.InitPeerCache(nil) + if err != nil { + slog.Error( + err, + "failed to init peer cache", + ) + return + } + + loadRelayServers(config) + + err = sessions.InitUserSessionCache() + if err != nil { + slog.Error( + err, + "failed to init user session cache", + ) + return + } + + if err := proxy.InitUnixCacheRoundTripper(); err != nil { + slog.Error( + err, + "failed to init unix cached round tripper", + ) + return + } + + if err := proxy.InitPeerCacheRoundTripper(); err != nil { + slog.Error( + err, + "failed to init unix cached round tripper", + ) + return + } + + // Start listeners + for _, listener := range ServerListeners { + switch listener.Protocol { + case utils.HTTPS: + go listener.StartHTTPSListen(ctx) + default: + slog.Error( + nil, + "unknown protocol", + "listener.Protocol", listener.Protocol, + "Addr", listener.Addr, + ) + } + } + + go StartPeeringMgr(ctx, log, exitChan, config) + + StartDialinPoolMgr(ctx, log, exitChan) + + for { + select { + case <-ctx.Done(): + slog.Error( + ctx.Err(), + "stoping Server", + ) + return + } + } +} + +type flushWriter struct { + w io.Writer + lg *relaylogger.RelayLog + c net.Conn +} + +func (fw flushWriter) Write(p []byte) (n int, err error) { + n, err = fw.w.Write(p) + fw.lg.Debug( + "data wrote", + "len", len(p), + "wrote", n, + "data", string(p), + "err", err, + ) + if f, ok := fw.w.(http.Flusher); ok { + f.Flush() + } + // tolerate max read/write idle timeout + fw.c.SetDeadline(time.Now().Add(utils.IdleTimeout)) + return +} + +func isConnectionExist(key string) bool { + connectionsRWMu.RLock() + defer connectionsRWMu.RUnlock() + _, ok := connections[key] + return ok +} + +func addConnection(key string, rconn *RelayConn) { + connectionsRWMu.Lock() + defer connectionsRWMu.Unlock() + connections[key] = rconn +} + +func deleteConnections(key string) { + connectionsRWMu.Lock() + defer connectionsRWMu.Unlock() + delete(connections, key) +} + +//DialinMetric for cluster connection +func DialinMetric(w http.ResponseWriter) { + for _, dsrv := range Servers { + if dsrv.Type == utils.DIALIN { + // look dialin pool to find dilain connection key + dsrv.DialinPool.GetDialinMetrics(w) + } + } +} diff --git a/components/relay/pkg/tunnel/server_config.go b/components/relay/pkg/tunnel/server_config.go new file mode 100644 index 0000000..148ecd5 --- /dev/null +++ b/components/relay/pkg/tunnel/server_config.go @@ -0,0 +1,43 @@ +package tunnel + +// Dialin defines a dialin. +type Dialin struct { + Protocol string + Addr string + ServerName string + RootCA []byte + ServerCRT []byte + ServerKEY []byte + Version string +} + +// Relay defines a relay. +type Relay struct { + Protocol string + Addr string + DialinSfx string + ServerName string + RootCA []byte + ServerCRT []byte + ServerKEY []byte + Version string +} + +// ControllerInfo defines controller info. +type ControllerInfo struct { + Addr string + PeerProbeSNI string + RootCA string + ClientCRT string + ClientKEY string +} + +// ServerConfig is the configuration for relay server +type ServerConfig struct { + RelayAddr string + Relays map[string]*Relay + CDRelays map[string]*Relay + Dialins map[string]*Dialin + Controller ControllerInfo + AuditPath string +} diff --git a/components/relay/pkg/utils/utils.go b/components/relay/pkg/utils/utils.go new file mode 100644 index 0000000..f0aa8bf --- /dev/null +++ b/components/relay/pkg/utils/utils.go @@ -0,0 +1,713 @@ +package utils + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "net" + "net/http" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/RafaySystems/rcloud-base/components/relay/pkg/relaylogger" + "github.com/dgraph-io/ristretto" + "github.com/felixge/tcpkeepalive" + "github.com/google/uuid" +) + +// Known relay services +const ( + KUBECTL = "kubectl" + KUBEWEB = "kubeweb" + GENTCP = "tcp" + HTTPUP = "httpupgrade" +) + +// Known server types +const ( + RELAY = "relay" + CDRELAY = "cdrelay" + RELAYAGENT = "relay-agent" + CDRELAYAGENT = "cdrelay-agent" + DIALIN = "dialin" + JoinString = "--" +) + +// Relay Network Types +const ( + KUBECTLCORE = "rafay-core-relay-agent" + KUBECTLDEDICATED = "rafay-non-core-relay-agent" + CDAGENTCORE = "rafay-core-cd-relay-agent" +) + +//SNICertificate sni based certs +type SNICertificate struct { + CertFile []byte + KeyFile []byte +} + +//Relaynetwork configmap data +type Relaynetwork struct { + Token string `json:"token"` // bootstrap agent token + Addr string `json:"addr"` // bootstrap register host + Domain string `json:"endpoint"` // dialout domain + Name string `json:"name"` // network name + TemplateToken string `json:"templateToken"` // bootstrap template token + Upstream string `json:"upstream"` // upstream tcp service host:port +} + +//RelayNetworkConfig config for relay agent +type RelayNetworkConfig struct { + // Network configmap + Network Relaynetwork + // RelayAgentCert used for relay-agent client cert + RelayAgentCert []byte + // RelayAgentKey used for relay-agent client cert + RelayAgentKey []byte + // RelayAgentCACert used for relay-agent client cert + RelayAgentCACert []byte +} + +// Known protocol types. +const ( + HTTP = "HTTP" + HTTPS = "https" + TCP = "tcp" + UNIX = "unix" +) + +// Known dialin types +const ( + KUBECTLDILAIN = "kubectldialin" + KUBEWEBDIALIN = "kubewebdialin" + PEERKEY = "04112005676520746869732070617373776f726420746f206120736563726574" +) + +const ( + //HeaderError .. + HeaderError = "X-Error" + //HeaderAction ... + HeaderAction = "X-Action" + //HeaderForwardedHost .. + HeaderForwardedHost = "X-Forwarded-Host" + //HeaderForwardedService .. + HeaderForwardedService = "X-Forwarded-Service" + + //HeaderRafayUserName .. + HeaderRafayUserName = "X-Rafay-User" + //HeaderRafayNamespace .. + HeaderRafayNamespace = "X-Rafay-Namespace" + //HeaderRafayScope .. + HeaderRafayScope = "X-Rafay-Scope" + //HeaderRafayAllow .. + HeaderRafayAllow = "X-Rafay-Allow" + //HeaderRafayAuthZSA yaml contains service account + HeaderRafayAuthZSA = "X-Rafay-AuthzSA" + //HeaderRafayAuthZRole yaml contains role + HeaderRafayAuthZRole = "X-Rafay-AuthzRole" + //HeaderRafayAuthZRoleBinding yaml contains rolebinding + HeaderRafayAuthZRoleBinding = "X-Rafay-AuthzRoleBinding" + //HeaderRafayServiceAccountNoExpire don't expire service account + HeaderRafayServiceAccountNoExpire = "X-Rafay-ServiceAccount-NoExpire" + //HeaderClearSecret to clear the current secret cache of user + HeaderClearSecret = "X-Rafay-Clear-Cache" +) + +// Known actions. +const ( + ActionProxy = "proxy" + + // DefaultAuditPolicyPath default audit policy filter path + // k8s audit need a file path + DefaultAuditPolicyPath = "./relayaudit.yaml" + + //DefaultAuditPath defailt audit log files path + DefaultAuditPath = "-" // - means standard out + + //RafayRelayServiceAccountNameSpace namespace used to create service account for relays + RafayRelayServiceAccountNameSpace = "system-sa" +) + +var ( + // LogLevel loglevel set from commadline + LogLevel int + // Mode relay/relay-agent + Mode string + // ClusterID unique id of the cluster + ClusterID string + // AgentID unique id for cd agent + AgentID string + // ExitChan trigger this channel to exit + ExitChan = make(chan bool) + // TerminateChan trigger this channel to exit + TerminateChan = make(chan bool) + // IdleTimeout is the maximum amount of time to wait for the + // next read/write before closing connection. + IdleTimeout = 5 * time.Minute + // DefaultTimeout specifies a general purpose timeout. + DefaultTimeout = 5 * time.Minute + // DefaultPingTimeout specifies a ping timeout. + DefaultPingTimeout = 5 * time.Second + + // DefaultKeepAliveIdleTime specifies how long connection can be idle + // before sending keepalive message. + DefaultKeepAliveIdleTime = 15 * time.Second + + // DefaultKeepAliveCount specifies maximal number of keepalive messages + // sent before marking connection as dead. + DefaultKeepAliveCount = 3 + + // DefaultKeepAliveInterval specifies how often retry sending keepalive + // messages when no response is received. + DefaultKeepAliveInterval = 5 * time.Second + + //DefaultMuxTimeout specifies vmux timeout + DefaultMuxTimeout = 10 * time.Second + + //UNIXSOCKET prefix path for unix socket + UNIXSOCKET = "/tmp/relay-unix-" // need to change this from tmp to appropriate path after integration + + //UNIXAGENTSOCKET prefix path for unix socket + UNIXAGENTSOCKET = "/tmp/relay-agent-unix-" // need to change this from tmp to appropriate path after integration + + //ProxyProtocolSize Default PROXY PROTO buffer size + ProxyProtocolSize = 1024 + + //RelayUUID runtime Unique ID for relay + RelayUUID string + + //RelayIPFromConfig IP address of the relay for peering + RelayIPFromConfig string + + //PeerCache stores peer dialin info + PeerCache *ristretto.Cache + + //ServiceAccountCache stores service account, role, role binding in relay-agetn in connector + ServiceAccountCache *ristretto.Cache + + //ServiceAccountCacheDefaultExpiry default expiry + ServiceAccountCacheDefaultExpiry = 600 * time.Second + + //PeerCacheDefaultExpiry default expiry + PeerCacheDefaultExpiry = 600 * time.Second + //PeerHelloInterval heartbeat interval + PeerHelloInterval = 60 * time.Second + //PeerServiceURI is the URI to join peering service + PeerServiceURI string + //PeerCertificate used for peering service communication + PeerCertificate []byte + //PeerPrivateKey used for peering service communication + PeerPrivateKey []byte + //PeerCACertificate used for peering service communication + PeerCACertificate []byte + + //RelayUserCert used for user/peer communication + RelayUserCert []byte + //RelayUserKey used for user/peer communication + RelayUserKey []byte + //RelayUserCACert used for user/peer communication + RelayUserCACert []byte + + //RelayUserPort user facing seerver port + RelayUserPort int + // RelayUserHost user facing seerver host (domain) + RelayUserHost string + //RelayConnectorCert used for relay-connector termination + RelayConnectorCert []byte + //RelayConnectorKey used for relay-connector termination + RelayConnectorKey []byte + //RelayConnectorCACert used for relay-connector termination + RelayConnectorCACert []byte + // RelayConnectorHost connector facing server host (domain) + RelayConnectorHost string + // RelayConnectorPort connector facing server port + RelayConnectorPort int + + // CDRelayUserCert used for client/peer communication + CDRelayUserCert []byte + // CDRelayUserKey used for client/peer communication + CDRelayUserKey []byte + // CDRelayUserCACert used for client/peer communication + CDRelayUserCACert []byte + // CDRelayUserHost client facing server host + CDRelayUserHost string + // CDRelayUserPort client facing server port + CDRelayUserPort int + // CDRelayConnectorCert used for cd-relay-connector termination + CDRelayConnectorCert []byte + // CDRelayConnectorKey used for cd-relay-connector termination + CDRelayConnectorKey []byte + // CDRelayConnectorCACert used for cd-relay-connector termination + CDRelayConnectorCACert []byte + // CDRelayConnectorHost connector facing server host (domain) + CDRelayConnectorHost string + // CDRelayConnectorPort connector facing server port + CDRelayConnectorPort int + + //RelayNetworks list of relaynemtworks from configmap + RelayNetworks []Relaynetwork + // RelayAgentConfig map of relay agent configurations + RelayAgentConfig map[string]RelayNetworkConfig + + // MaxDials max connections dialed + MaxDials = 10 + + // MinDials minimum connections dialed + MinDials = 8 + + //PODNAME name of the pod + PODNAME string + + // DialoutProxy setting used while connecting to relay IP:PORT or HOST:PORT format + DialoutProxy = "" + + // DialoutProxyAuth Proxy-Authorization header base64 encoded value of user:password + DialoutProxyAuth = "" + + // DefaultTCPUpstream default TCP upstream + DefaultTCPUpstream = "127.0.0.1:16001" + + // ScalingStreamsThreshold concurrent streams count to trigger scaling + ScalingStreamsThreshold = 400 + + // ScalingStreamsRateThreshold new streams rate to trigger scaling + ScalingStreamsRateThreshold = 200 + + // MaxScaleMultiplier multiplier to limit max scaled connections + MaxScaleMultiplier = 3 + + // HealingInterval time to close idle scaled connection + HealingInterval = 24 // Hour +) + +//CountWriter to measure bytes +type CountWriter struct { + W io.Writer + Count int64 +} + +// ControlMessage is sent from server to client before streaming data. It's +// used to inform client about the data and action to take. Based on that client +// routes requests to backend services. +type ControlMessage struct { + Action string + ForwardedHost string + ForwardedService string + RemoteAddr string + RafayUserName string + RafayNamespace string + RafayScope string + RafayAllow string + RafayAuthz string +} + +// ProxyConfig configs for the proxy +type ProxyConfig struct { + Protocol string + Addr string + ServiceSNI string + RootCA string + ClientCRT string + ClientKEY string + Upstream string + UpstreamClientCRT string + UpstreamClientKEY string + UpstreamRootCA string + UpstreamSkipVerify bool + UpstreamKubeConfig string + Version string +} + +//ProxyProtocolMessage used across dialin unix socket +type ProxyProtocolMessage struct { + DialinKey string + UserName string + SNI string +} + +type ServiceAccountCacheObject struct { + RafayAuthzSA string + RafayAuthzRole string + RafayAuthzRoleBind string + Md5sum string + Key string +} + +//OnEvict cache on eviction call back function +type OnEvict = func(item *ristretto.Item) + +//Fatal to exit the program +func Fatal(format string, a ...interface{}) { + fmt.Fprintf(os.Stderr, format, a...) + fmt.Fprint(os.Stderr, "\n") + os.Exit(1) +} + +//CloneHeader clone http headers +func CloneHeader(h http.Header) http.Header { + h2 := make(http.Header, len(h)) + for k, vv := range h { + vv2 := make([]string, len(vv)) + copy(vv2, vv) + h2[k] = vv2 + } + return h2 +} + +//SetXForwardedFor ... +func SetXForwardedFor(h http.Header, remoteAddr string) { + clientIP, _, err := net.SplitHostPort(remoteAddr) + if err == nil { + // If we aren't the first proxy retain prior + // X-Forwarded-For information as a comma+space + // separated list and fold multiple headers into one. + if prior, ok := h["X-Forwarded-For"]; ok { + clientIP = strings.Join(prior, ", ") + ", " + clientIP + } + h.Set("X-Forwarded-For", clientIP) + } +} + +//SetXRAYUUID ... +func SetXRAYUUID(h http.Header) { + var reluuid string + + reluuid = RelayUUID + // If this isn't the first relay retain prior + // UUIDs information as a comma+space + // separated list and fold multiple headers into one. + if prior, ok := h["X-Rafay-XRAY-RELAYUUID"]; ok { + reluuid = strings.Join(prior, ", ") + ", " + reluuid + } + h.Set("X-Rafay-XRAY-RELAYUUID", reluuid) +} + +//SetXForwardedRafay set rafay headers +func SetXForwardedRafay(h http.Header, msg *ControlMessage) { + h.Set("X-Rafay-User", msg.RafayUserName) + h.Set("X-Rafay-Namespace", msg.RafayNamespace) + h.Set("X-Rafay-Scope", msg.RafayScope) + h.Set("X-Rafay-Allow", msg.RafayAllow) +} + +//UnSetXForwardedRafay set rafay headers +func UnSetXForwardedRafay(h http.Header) { + h.Del("X-Rafay-Scope") + h.Del("X-Rafay-Allow") + h.Del("X-Forwarded-For") + h.Del("X-Rafay-XRAY-RELAYUUID") + h.Del("X-Rafay-Audit") + h.Del("X-Rafay-Cluster-Id") + h.Del("X-Rafay-Cluster-Servername") + h.Del("X-Rafay-Peer-Hash") + h.Del("X-Rafay-Peer-Nonce") + h.Del("X-Rafay-Sessionkey") + h.Del("X-Rafay-User-Cert-Issued") +} + +//Transfer transfer by io.Copy +func Transfer(dst io.Writer, src io.Reader, tlog *relaylogger.RelayLog, direction string) { + n, err := io.Copy(dst, src) + if err != nil { + if !strings.Contains(err.Error(), "context canceled") && !strings.Contains(err.Error(), "CANCEL") && !strings.Contains(err.Error(), "i/o timeout") && !strings.Contains(err.Error(), "closed network") { + tlog.Error( + err, + "io.Copy error", + "direction", direction, + ) + } + } + + tlog.Debug( + "action transferred", + "bytes", n, + "in direction", direction, + ) +} + +// WriteToHeader writes ControlMessage to HTTP header. +func WriteToHeader(h http.Header, c *ControlMessage) { + h.Set(HeaderAction, string(c.Action)) + h.Set(HeaderForwardedHost, c.ForwardedHost) + h.Set(HeaderForwardedService, c.ForwardedService) + h.Set(HeaderRafayUserName, c.RafayUserName) + h.Set(HeaderRafayNamespace, c.RafayNamespace) + h.Set(HeaderRafayScope, c.RafayScope) + h.Set(HeaderRafayAllow, c.RafayAllow) +} + +func (cw *CountWriter) Write(p []byte) (n int, err error) { + n, err = cw.W.Write(p) + cw.Count += int64(n) + return +} + +//CopyHeader copy header +func CopyHeader(dst, src http.Header) { + for k, v := range src { + vv := make([]string, len(v)) + copy(vv, v) + dst[k] = vv + } +} + +// ReadControlMessage reads ControlMessage from HTTP headers. +func ReadControlMessage(r *http.Request) (*ControlMessage, error) { + msg := ControlMessage{ + Action: r.Header.Get(HeaderAction), + ForwardedHost: r.Header.Get(HeaderForwardedHost), + ForwardedService: r.Header.Get(HeaderForwardedService), + RafayUserName: r.Header.Get(HeaderRafayUserName), + RafayNamespace: r.Header.Get(HeaderRafayNamespace), + RafayScope: r.Header.Get(HeaderRafayScope), + RafayAllow: r.Header.Get(HeaderRafayAllow), + RemoteAddr: r.RemoteAddr, + } + + var missing []string + + if msg.Action == "" { + missing = append(missing, HeaderAction) + } + if msg.ForwardedHost == "" { + missing = append(missing, HeaderForwardedHost) + } + if msg.ForwardedService == "" { + missing = append(missing, HeaderForwardedService) + } + + if len(missing) != 0 { + return nil, fmt.Errorf("missing headers: %s", missing) + } + + return &msg, nil +} + +//FlushWriter flush writer +type FlushWriter struct { + W io.Writer +} + +func (fw FlushWriter) Write(p []byte) (n int, err error) { + n, err = fw.W.Write(p) + if f, ok := fw.W.(http.Flusher); ok { + f.Flush() + } + return +} + +//KeepAlive set keepalive +func KeepAlive(conn net.Conn) error { + return tcpkeepalive.SetKeepAlive(conn, DefaultKeepAliveIdleTime, DefaultKeepAliveCount, DefaultKeepAliveInterval) +} + +//GenUUID generates a google UUID +func GenUUID() { + id := uuid.New() + RelayUUID = id.String() +} + +//GetRelayIP get relay IP address +func GetRelayIP() string { + if RelayIPFromConfig == "" { + name, err := os.Hostname() + if err == nil { + addr, err := net.LookupIP(name) + if err == nil { + return addr[0].String() + } + } else { + return "" + } + } + return RelayIPFromConfig +} + +// GetRelayIPPort get relay IP:PORT of user facing server +func GetRelayIPPort() string { + if RelayIPFromConfig == "" { + return GetRelayIP() + ":" + strconv.Itoa(RelayUserPort) + } + return RelayIPFromConfig +} + +//CheckRelayLoops :does XRAY UUDI already present in header? +func CheckRelayLoops(h http.Header) bool { + if uuidHdr, ok := h["X-Rafay-XRAY-RELAYUUID"]; ok { + allIds := strings.Join(uuidHdr, ", ") + matched, err := regexp.Match(RelayUUID, []byte(allIds)) + if err == nil && matched { + return true + } + } + return false +} + +// InitCache initialize the cache to store dialin cluster-connection +// information of peers. When a dialin miss happens look into this cache +// to find the peer IP address to forward the user connection. +func InitCache(evict OnEvict) (*ristretto.Cache, error) { + return ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, // Num keys to track frequency of (10M). + MaxCost: 1 << 30, // Maximum cost of cache (1GB). + BufferItems: 64, // Number of keys per Get buffer. + OnEvict: evict, + }) +} + +// InsertCache inserts the value to cache +func InsertCache(cache *ristretto.Cache, expiry time.Duration, key, value interface{}) bool { + return cache.SetWithTTL(key, value, 100, expiry) +} + +// GetCache get value from cache +func GetCache(cache *ristretto.Cache, key interface{}) (interface{}, bool) { + return cache.Get(key) + +} + +// DeleteCache delete value from cache +func DeleteCache(cache *ristretto.Cache, key interface{}) { + cache.Del(key) +} + +//WriteFile overwrite if exist +func WriteFile(filename, data string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + + defer file.Close() + + file.WriteString(data) + return nil +} + +// validOptionalPort reports whether port is either an empty string +// or matches /^:\d*$/ +func validOptionalPort(port string) bool { + if port == "" { + return true + } + if port[0] != ':' { + return false + } + for _, b := range port[1:] { + if b < '0' || b > '9' { + return false + } + } + return true +} + +// SplitHostPort separates host and port. If the port is not valid, it returns +// the entire input as host, and it doesn't check the validity of the host. +func SplitHostPort(hostport string) (host, port string) { + host = hostport + + colon := strings.LastIndexByte(host, ':') + if colon != -1 && validOptionalPort(host[colon:]) { + host, port = host[:colon], host[colon+1:] + } + + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { + host = host[1 : len(host)-1] + } + + return +} + +// IsHTTPS returns true if port is 443 +func IsHTTPS(addr string) bool { + _, port, err := net.SplitHostPort(addr) + if err == nil { + if port == "443" { + return true + } + } + return false +} + +// PeerSetHeaderNonce header +func PeerSetHeaderNonce(h http.Header) error { + key, _ := hex.DecodeString(PEERKEY) + + data := h.Get("X-Rafay-XRAY-RELAYUUID") + if data == "" { + return fmt.Errorf("no X-Rafay-XRAY-RELAYUUID header") + } + value := []byte(data) + + block, err := aes.NewCipher(key) + if err != nil { + return err + } + + nonce := make([]byte, 12) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return err + } + + ciphertext := aesgcm.Seal(nil, nonce, value, nil) + hash := hex.EncodeToString(ciphertext) + + peerNonce := hex.EncodeToString(nonce) + h.Set("X-Rafay-Peer-Nonce", peerNonce) + h.Set("X-Rafay-Peer-Hash", hash) + return nil +} + +// CheckPeerHeaders validates upstreams request +func CheckPeerHeaders(h http.Header) bool { + key, _ := hex.DecodeString(PEERKEY) + + hash := h.Get("X-Rafay-Peer-Hash") + if hash == "" { + return false + } + + peerNonce := h.Get("X-Rafay-Peer-Nonce") + if peerNonce == "" { + return false + } + + expected := h.Get("X-Rafay-XRAY-RELAYUUID") + if expected == "" { + return false + } + + ciphertext, _ := hex.DecodeString(hash) + block, err := aes.NewCipher(key) + if err != nil { + return false + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return false + } + + nonce, _ := hex.DecodeString(peerNonce) + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return false + } + + if string(plaintext) == expected { + return true + } + + return false +} diff --git a/components/relay/testdata/README.md b/components/relay/testdata/README.md new file mode 100644 index 0000000..c0c7c4d --- /dev/null +++ b/components/relay/testdata/README.md @@ -0,0 +1,89 @@ +# rafay-relay unit testing +k8s api server relay unit testing + + +## Environment +Please refer below image to setup your test environtment. You can run server and client in the same system. + +![](images/testsetup.png) + + +## Testing +Add entries in /etc/host or in your DNS-server for the relay-server ipaddress.\ +The User facing DNS is **cluster1.kubectl.relay.rafay.dev**\ +The Dialin facing DNS is **cluster1.kubectldialin.relay.rafay.dev**\ +For example; to run client and server in local system, the /etc/host will have entries like below. +``` +127.0.0.1 cluster1.kubectl.relay.rafay.dev +127.0.0.1 cluster1.kubectldialin.relay.rafay.dev +``` + +### Client System setup +Makesure $KUBECONFIG environment variable is set in the client system\ +(OR) +You can use testdata/kubectlconfigs/kubeconfig and customise it for your setup. +``` +export KUBECONFIG=testdata/kubectlconfigs/kubeconfig +``` + +### Cluster serviceaccount setup +Apply config pkg/proxy/testdata/role.yaml +``` +kubectl apply -f pkg/proxy/testdata/role.yaml +``` + +Run service using below commands +``` +sudo relay.exe -config ./testdata/server.yaml start server +``` +### Relay client for cluster1 +``` +relay.exe -config ./testdata/client-cluster1.yaml start client + +``` +### Relay client for cluster2 +``` +relay.exe -config ./testdata/client-cluster2.yaml start client + +``` + +### Using Relay for kubectl +In kubeconfig set server address to relay.\ +Relay uses MTLS to authenticate. Set `client-certificate` and `client-key` to cert signed by the relay service rootCA. For kubectl to validate the relay server set `certificate-authority`. Use `--insecure-skip-tls-verify` to skip the server validation. + +Examples: +``` +kubectl --kubeconfig testdata/kubectlconfigs/kubeconfigproxy-cluster1 get pods +kubectl --kubeconfig testdata/kubectlconfigs/kubeconfigproxy-cluster2 get pods +kubectl --kubeconfig testdata/kubectlconfigs/kubeconfigproxy-cluster1 exec -it demo-sa1 -- /bin/sh +kubectl --kubeconfig testdata/kubectlconfigs/kubeconfigproxy-cluster2 exec -it demo-sa1 -- /bin/sh +``` + +### Production +Relay server need following environment variables + +POD_NAME - name of the relay instaance (auto generated) +POD_NAMESPACE - namespace of the relay instance + +RAFAY_SENTRY - URL to boostrap with sentry service +RAFAY_RELAY_PEERSERVICE - To join gRPC with sentry peer service + + +RELAY_USER_DOMAIN - domain suffix for relay user facing server +RELAY_CONNECTOR_DOMAIN - domain suffix for relay connector facing domain + +RELAY_TOKEN - Peer Service Token bootstrapping +RELAY_USER_TOKEN - Relay user facing server bootstrapping +RELAY_CONNECTOR_TOKEN - Relay connector facing server bootstrapping + +Examples: +``` +export RAFAY_SENTRY=http://sentry.rafay.dev:9000 +export RAFAY_RELAY_PEERSERVICE=http://peering.sentry.rafay.local:7001 + +export RELAY_USER_DOMAIN="user.relay.rafay.local" +export RELAY_CONNECTOR_DOMAIN="core-connector.relay.rafay.local" + +export RELAY_TOKEN=bqfvhabipt3a2g46986g +export RELAY_USER_TOKEN=bqfvhabipt3a2g46987g +export RELAY_CONNECTOR_TOKEN=bqfvhabipt3a2g469870 diff --git a/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt new file mode 100644 index 0000000..abeb56a --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEWjCCAkICCQD0kF4yo85tQzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDEwOTE5MTUx +NVoXDTMwMDEwNjE5MTUxNVowgYAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTET +MBEGA1UEBwwKUGxlYXNhbnRvbjEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0Rl +djExMC8GA1UEAwwocmVsYXlzZXJ2ZXIxLUFCQ0QtMTIzNDU2LnJlbGF5LnJhZmF5 +LmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN51AZQAYIoAAQUk +G7qFudNDMT03FO6j+ThIsqQzqXgHOCBDVMhaWs4qKJzMD+8vtnPHzz2t0ksYsnKj +YfO21C/kzbMz3yBZSyFA/eqEA/CLbyzECs+eaNHX7hDaVcHX22Rbi+W7zmjJE2LZ +5oxGoQKnSQFEn/0SsjKX4DdF+Rlw4TBgBuJ77tXnslZr+Hcgrt8dOApuWZ6v5PPN +aM72L1Gp1eP7ViUia8yc4AO2UjdO8iGSaDjy/mlWVLsK9Lb+/O5RGMmqcN+o6Mnd +U6WMOtVgkjfNeIZWJ0RdmEkR/JMyOmZauyqZiW7stOVhtOxfhE5b2N7VbqtT989q +OPZ/x38CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAng07c+Kn65mvGawiV3ZICuTy +cpfnlPEmpx4ZtDUsTIyRgaRkPpmPFkJee8kNPIrY68EeLmQB6oA976cx6+N9B61U +2oBG1HpbdtTOwvj9qid3B+syzDJXmgpEWczsNAptDqOfa/4jGzp53rORfljXnqBx +DwJhe5XI6ZJ0DLK1piNneq076qElylPgCV//tzyNn9mjaNCl64FjJyhER36UWEm7 +o4JCQkprppyocWs/NKq6ENsNsNPdWH+ZxiVy2eIGOGMk6zWu8VcCGaRDQDumkQT3 +yMh4DBfyCfbd8liOYLsLuYkGaz0Ew76TgMr875v4t53VtudNdREEqDkkLfNJA2hQ +WwgLylCSqmaWL8V10OvlLG+EnT0XEIkHbzRW5XYxs+U7saG6ze5wnpPNWDb8MYj6 +4bV6V4My5o7dejDBVzhk894MtOjdrhLWCUuZ0WcZvfIm4z5c6jWuKio3VB2hcVn+ +2rtLu7LFWhlmyiX2TtB3cx1gkGpYTllQGdWkmsQMR0uxVy1KVyiotrlK5oDBuoW/ +/XhQ1pKUL9gW1PeTrUexUJBxFP+MWONOuzarDi+fGNyDCh8wgkFkYotExGUiVxjT +9h/mc7fSVypygrWkZmtjb2gAJQRBH1GOu3fhr1MX8AXY3wtZXMWCDmoad02PZvrB +Rnx4d4QuHqlO8LsMvyo= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.csr b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.csr new file mode 100644 index 0000000..0ba4a6b --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICxjCCAa4CAQAwgYAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTETMBEGA1UE +BwwKUGxlYXNhbnRvbjEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjExMC8G +A1UEAwwocmVsYXlzZXJ2ZXIxLUFCQ0QtMTIzNDU2LnJlbGF5LnJhZmF5LmRldjCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN51AZQAYIoAAQUkG7qFudND +MT03FO6j+ThIsqQzqXgHOCBDVMhaWs4qKJzMD+8vtnPHzz2t0ksYsnKjYfO21C/k +zbMz3yBZSyFA/eqEA/CLbyzECs+eaNHX7hDaVcHX22Rbi+W7zmjJE2LZ5oxGoQKn +SQFEn/0SsjKX4DdF+Rlw4TBgBuJ77tXnslZr+Hcgrt8dOApuWZ6v5PPNaM72L1Gp +1eP7ViUia8yc4AO2UjdO8iGSaDjy/mlWVLsK9Lb+/O5RGMmqcN+o6MndU6WMOtVg +kjfNeIZWJ0RdmEkR/JMyOmZauyqZiW7stOVhtOxfhE5b2N7VbqtT989qOPZ/x38C +AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQDYnQFv2aG7pN3idts3EkuIlBlfVGKM +V1gQjasdkdVlBVS0ehCRCXOzey3kgNtQiFF1YHZ/0uoq1abZjRCXg4EG5++mTuMk +yIB3Yng1bEJ8A4juRrWn2ihEul7sfPfEt0lfxqn2sqN5ghC1UpbcPh6T74mbIflq +pMdv2IDDpOzCNAOHSqjbJJTg1oCi+nMabXOtwn0pqsC1tiqROLtG8qWV1WlT59zb +9FxDeIhT/pbZNSlwJ7akJ7ykeIhrQdSQpnclvzum0/vO0aLoojQg0UH9ldEHGu0K +Pn+k1eX3xOD9KZn7qrgu0qZR0v+18brJ9jPTdxWKyMLhVmGX6Fzj4zIL +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key new file mode 100644 index 0000000..92c3e67 --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3nUBlABgigABBSQbuoW500MxPTcU7qP5OEiypDOpeAc4IENU +yFpazioonMwP7y+2c8fPPa3SSxiycqNh87bUL+TNszPfIFlLIUD96oQD8ItvLMQK +z55o0dfuENpVwdfbZFuL5bvOaMkTYtnmjEahAqdJAUSf/RKyMpfgN0X5GXDhMGAG +4nvu1eeyVmv4dyCu3x04Cm5Znq/k881ozvYvUanV4/tWJSJrzJzgA7ZSN07yIZJo +OPL+aVZUuwr0tv787lEYyapw36joyd1TpYw61WCSN814hlYnRF2YSRH8kzI6Zlq7 +KpmJbuy05WG07F+ETlvY3tVuq1P3z2o49n/HfwIDAQABAoIBAFxDMPVeNqvlloa4 +VBvHDKwXSRu3daIINfvnAthhaN8UN24CHu69sr2g9JkbdVxhcI2SiFxvdDGbO5+a +Lazz3gtDHSE+2ULEZgmTt/wE5tFkMybg5A76B0VJBj7NHxY3XZHA+kO84b3J75Vn +BFyXin3cPcYd3TEMCQY8wUyLY+Xtii6yrA618H/27Dhe/F04/MHchMf1VlI2zQxI +/Yi3snIAl3YFRVb+TxQCd1LG3UvbafPRTc2zv9HiXQey7ahwbtzyikQwOZT0htJg +iTy72nJVHq5Iel1L0/gwD/0m91QrlZlTqbfkCvRZCKNgtaf2+wJ+/HWIoLF8SlqX +MO9DWEkCgYEA8kisxKhEoAVj4e2mQmuLSzc1zB+/kPtWCXSnc/gTvQPC2LMlwk8k +8IRVah4SvqdgLgXXCbARRatUj43yetPZXlKF45OXFs/39abpFyFWG2Dzcwmy1gYC +hHOFYieZWvinYs5mvBuG0qLnSpw0sTWsUvuJB+jp1M+W/6pvaw8O8fUCgYEA6wz9 +I3Q+E+KEz4o+P+PhxbFd6F3BRPIT6hDn/EOISuaZ/8Zo1t0fhzjH5TXL9ipeNKfF +VmNE4enKg3uNAYR0PbWFWZjLRn9pG9WIKh6ws28eTxjJ8RSlHJELXsc+cLFkXtKu +9Z3R3Fzw+j3JKTE8bXbu99WrzIv72QIu5tIbByMCgYEA3KgxqR1y+qZOFIqi/CUE +BKmwtkpH5tvwvMXgaC8Mkv2k7EUkY3Y/N0nenLns6nR3lRK+iNUwtdnXNrLZnuHo +Ow4/YEefuCgKNVEhQm6bBG5k6mVv2Zo5mrW3SfC0q3SAxe0jBbiwLpAsw/fFyYsP +ebgaUfjfVXEGnax0svVQ8C0CgYBt/P77NyrRTT8OiscESJNeWdfyouSMaabN8alM +jyFYwz2uxV5LpN9a/EP1ys2tXDgG9bbO7NbPzAG6svbRjfSC580Flw0P6RD7OFlm +NERFQqrxIk1k5V851dl3OU7Llwa7pGTAhGAueMM+pejawLJhGrVJ/Y9NwPdRncbq +tch19wKBgD1Ua1yiYWbKPP3l9btMwjKUJlD08+4JXCTSVKzKe/JclQ+fhnpBZZGS +58zgrUugfpdOnzavGxs9kKVskl3nNGtpGtuWyY8fSIU/qQQXEJRcCYpcgu8bMJNI +eAo8PrJR6BU9nGuKBBhEs+PWt1LiNaTLqU1GMMnwneqsrCwKPOlG +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/clientcerts/star.admin.crt b/components/relay/testdata/certs/clientcerts/star.admin.crt new file mode 100644 index 0000000..f9c0a02 --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.admin.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIELjCCAhYCCQD0kF4yo85tTTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDEyMzE3NDM0 +NloXDTMwMDEyMDE3NDM0NlowVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEOMAwGA1UE +AwwFYWRtaW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkOPicn8J1 +BQlrZLxAX815+T9IZC/Zb/KYv1VYo+7oRGpLivJbJEb7MRJkyqCN33/1bH4B4REv +56qF9LdLO0xMSErK9vXafqWis++HAPp1p7LSFmjRw75x7tKdzlUEkciqt3SBoxQj +wRwlgGBMHxqlJt/DhvQ8mxodxbdyWiOzU+gDHeexZqFrmoWe498ywU/l0ollBn5P +aaKPU39uaVcn6W2t0jkIioKKm8qiaA6bHAK3voflE/8+/6DVhVAjdacwidgHnS6z +xCSWGvENinXBN9zZBuL0mCxCKcOJGy1NGME+8IrCEJNI2MgL+hVFvCBXW+cgtSeI +imK/3ETfELHFAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAKr5EH4MBun6Hb1CQt7t +LqeDZ+J1KyvMV6AUPu8v3zReOG3YjQORurKxDeK/vI3QCrJLzrAUQodJM0eGEnRu +mG42UxUxZowKqg5uiEJVTMZEIOr8W1dpyQAEHlU7DbiarjnZAzdTSafquc0PU4uf +sBFK//lDluyVaetNC7f1Sn61Gh0piirvIXZW7z9K+HgheK3vz4r40RkjTDm3ZBUh +nPDU/DtkVJET/yM4VcdqggRr2XTah0CeDw6nUKB+gBw352q7upMI4KLkry/sjbQ1 +D151h2E1qkhG6gqnau+dmNcT6CKNVRQSAhLT4ULf4oc+IFvMhXi/I1Ghxqla2z3R +5DpIi9GF/efljKujshy3FrLLjCDZgUAl0qv936T5vZAQZ9SL/iY3cIUr9nOmcfFd +67lf040D3SJvELzEoS8bbCJ3VgBShSVc+TE5LQ0LC3DjdunWJgPAXmpKAkHhAM7P +ftU0jaLeLlonnV8xPYMRCsitb/3Xz43VImbAcrCVA6glu0bg/5CpEBS+2fIVpBVl +hKuVCKzDs5ei2q1H3f2ejOHy9PbCl8deAkhNwfk7RSsWaMk0CTU7WJXLindFQuQI +G2o6d0O4AQcQYlLaIlwCgY7O+HE9YBkfLMgiA28xtw2p4sDxuYzQBPuNt/FiT7ew +AL5fJTfMjVuWBjMr4XS84L+3 +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/clientcerts/star.admin.csr b/components/relay/testdata/certs/clientcerts/star.admin.csr new file mode 100644 index 0000000..794fcae --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.admin.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmjCCAYICAQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEOMAwGA1UEAwwFYWRt +aW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkOPicn8J1BQlrZLxA +X815+T9IZC/Zb/KYv1VYo+7oRGpLivJbJEb7MRJkyqCN33/1bH4B4REv56qF9LdL +O0xMSErK9vXafqWis++HAPp1p7LSFmjRw75x7tKdzlUEkciqt3SBoxQjwRwlgGBM +HxqlJt/DhvQ8mxodxbdyWiOzU+gDHeexZqFrmoWe498ywU/l0ollBn5PaaKPU39u +aVcn6W2t0jkIioKKm8qiaA6bHAK3voflE/8+/6DVhVAjdacwidgHnS6zxCSWGvEN +inXBN9zZBuL0mCxCKcOJGy1NGME+8IrCEJNI2MgL+hVFvCBXW+cgtSeIimK/3ETf +ELHFAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAmVSuFX+p7D/9tGfBgt9OKGn6 +N0xVc6d1gVf9ADrx1KEWjoqMnng2oe8yc5LXIfYHMZEIm9J0EigYsMEIUBDMer86 +CIXBdVN94H5gUQDIzaZ+M0+eU8vg9/K1GGFBkv6hBzV89Kps+1v3ykzsWlN7JcMM +HZYzUTrgeSAHiQ7CjVEjUMkcAkeF23YYdSsNcpDx1+8m/ptFaaaVzhGL/2XmvPjk +xCJDsZYX/DmHaHWcYRFUVxND9bDOEF7c6Gt+J9spHR7hUIDfeyADIlvItI2Rw4i7 +70Fg/JUCUG25KyD8BKSwBic6GAJ04g8+aE3d3FMRnPxc5iNdFJyRG/KGgNY0CQ== +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/clientcerts/star.admin.key b/components/relay/testdata/certs/clientcerts/star.admin.key new file mode 100644 index 0000000..7776b8c --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.admin.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA5Dj4nJ/CdQUJa2S8QF/Nefk/SGQv2W/ymL9VWKPu6ERqS4ry +WyRG+zESZMqgjd9/9Wx+AeERL+eqhfS3SztMTEhKyvb12n6lorPvhwD6daey0hZo +0cO+ce7Snc5VBJHIqrd0gaMUI8EcJYBgTB8apSbfw4b0PJsaHcW3clojs1PoAx3n +sWaha5qFnuPfMsFP5dKJZQZ+T2mij1N/bmlXJ+ltrdI5CIqCipvKomgOmxwCt76H +5RP/Pv+g1YVQI3WnMInYB50us8QklhrxDYp1wTfc2Qbi9JgsQinDiRstTRjBPvCK +whCTSNjIC/oVRbwgV1vnILUniIpiv9xE3xCxxQIDAQABAoIBADiWj752NodBenZw +geUMXCyUSsauYNbvVR3ew1C5RTG+VQTqLPHAawE5x6/KNkk3U8kM36mjntlb/gI9 +DX3P9Dkt662r0oYcxQE00H2PFos5y7Mf71mjyYMCjqKmWJpLc5Aw8Oh8ATBkX5BE +5pu5UqgEnZMj0Ud9xdn//l6STyvVeGDQCEtLPn9AGZFuZL/HpXKQgr1nfmSA4gMI +tDdDumVVlJL6efEdKF4whgS+ejGqsyJ7oB1MHWemqVtA4WYn8J2YL+XRN4JwVRJh +Pu5aDvHFfrn+2k33nmGWVIlXFl75v1pOCzYv+Aa7Bxx4PMvyU7WZONsJ2xjiJhp2 +OLiP4LkCgYEA844zPjAI1WWVe4HViYjggJrQZl20D0JBy22wqw55eqGSWX1gON3k +d6RapaVyThKd660Qmsdemgjr0owY/0Fr8JnM2ybL4/0MG9jBynZ27vbyLG1od9gW +96ewn5F0oyT8HT0bicz4EZfVEeUD1sqCCRIriFSccR/qzjVjt8qY+rsCgYEA7+I1 +2oKqXSvyDxlexVsKKPXoH1fyTpk3/uU5esyDatQJ2t4xlZ0xeOcvh0WsAmHJeax1 +nNHlv6RabiBtiNME6XMXI6oCE4yYeJy7l3UAOTB568n1oIED9Phq/dkoMsjs3qx0 +qSxfhYY98LOPObiF73Iar5Qw0FPvR3alvvnNfX8CgYEA68u7LrTgiIFd1IIhk5MS +D1BS/RgxECjMfD/JMALGd4RfpQYuF0A0ReS34fEolg+OFrKqBb+JhkWm0mPSe6YB +86FjZLTN5v+BLqCPFg3VHYu1cG/IGWb67W/LHuF688wFzeTweVwYuyetIDMBoXzT +oiaWQNnxz8/fyx6DWVfq/asCgYASLjvUpsiQrS74U/zpyj+X4SNJlNnJeTVUPT1B +AFUJeK6ShvqNE0/XkPX6M56Yw56IeTikvQfDoYo1BIIot42Mv+tGCbGEl8hKg953 +2Sc+Ll1fMHxMQNry6wz41keEqdPCAKIK3FEVBpqVLpaxUjncbvJ00i9s6p+c8/IV +6krXswKBgFfQ1lc2ffE62AcXJofw+uaNZM2+FUbaDEcjcAR/qPuRR9pGCNtnGsa+ +CkTsatI7Ip3CrqBIwcqTvl8agt+I/Fn+Zcdp956uaqyyEYvTEetFbdvaFSqf+krF +YvOkAt3xy3Am7dmT8Qx5+FrXBkRDtfYk3xh0tAMZV1H1jiKq9qcN +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/clientcerts/star.default.crt b/components/relay/testdata/certs/clientcerts/star.default.crt new file mode 100644 index 0000000..58ab928 --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.default.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUTCCAjkCCQD0kF4yo85tTDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExODE2MDA0 +NFoXDTMwMDExNTE2MDA0NFoweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEQMA4GA1UE +AwwHZGVmYXVsdDEfMB0GCSqGSIb3DQEJARYQZGVmYXVsdEByYWZheS5jbzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDzZVMm5TvFvMxMIryQGu/uTl84 +59lCgTEX6pr7H55OPg5/ZM5w/s66+qC3Teyw98i+X3PIywUizsnVkmylcLBRDLUY +/doo3Xp1KkTClasC2XeDIKl7zlNgIyqLLfRF0CkjdWsyFTpNi5NemezRDUBa9qXt +6EK5qKWL5SN3kelTZk3slOhfXyr/a2mEFMfqF6SYp2vZGBfF/h8mpb02TEivaTIN +haldIxDi0LaWXTqWfJf4UPCiHFtq78OGysA3LfFPfRvwe9WxQKbV7D88HC5H7EKA +gYjT7AFgSvl6vCRwj+DrCw3rkS6bUDrpOItp157n1KVaiQ1wnvi0oOcbI+MCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEAPQg5nhu35IcKrzFw8CfmUqY+w0qvxteaaULr +KHXJ7Fm9qoD02q+V4xxysFm2dP2R1Q+LqSzLnVIJapNQMpeAqfl4yiPZpBdPIMZK +2LPTD/A4o/4mhADte7pUtZSc0XuuZ+ZAb7wQis9ujkUxJk5S+uy7++zK5Pvh4e3/ +b463riy8GyOIRKipgEyTeLFz/VpjkqadRUZY59N4pFA1Hr1EjZOfdiVLWKlVT1iV +JyWijo8+SL5bt8naj8fKdnQNqZkHZSZ4Ep0lDRCxeOfNyUG27xsSPoa1ixKh+N3x +ipB2ZWy/0k87vmLEMc4Zr13o/iLEhDp3y5NuG/djwtnzd81/RFvD7fPjf+nmAzdI +XQe2VARmm8KeYvAEsyawQnVxx4cx/N8M+8VlF8PXS/rZGt6W3+OW2R4ko6Bi2R2K +efb10HKFi9uhalumrmMH24XriH0hQS9cyeSRTsjjWr3HUNrFb6/uAOZCoEBqPHJh +ohL1dwUKcxdM+ygptqIopCLNapXFbZur60V93N8SCVu+BbK02yg9AbdbaZh4vdCO +pmiIIzTcgkirfn8Cbp22513x7P/AE0bxxZlAJB+dPpgT3c5QFLaS5ImSoDWRkhFH +s0NBmbBYF/PkfApItiOWQU5ZNNOfn2TO94MKJMY9nNYBWT9xT45ON/qo+k50/ean +zrh9Cy8= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/clientcerts/star.default.csr b/components/relay/testdata/certs/clientcerts/star.default.csr new file mode 100644 index 0000000..ed6b228 --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.default.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICvTCCAaUCAQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEQMA4GA1UEAwwHZGVm +YXVsdDEfMB0GCSqGSIb3DQEJARYQZGVmYXVsdEByYWZheS5jbzCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALDzZVMm5TvFvMxMIryQGu/uTl8459lCgTEX +6pr7H55OPg5/ZM5w/s66+qC3Teyw98i+X3PIywUizsnVkmylcLBRDLUY/doo3Xp1 +KkTClasC2XeDIKl7zlNgIyqLLfRF0CkjdWsyFTpNi5NemezRDUBa9qXt6EK5qKWL +5SN3kelTZk3slOhfXyr/a2mEFMfqF6SYp2vZGBfF/h8mpb02TEivaTINhaldIxDi +0LaWXTqWfJf4UPCiHFtq78OGysA3LfFPfRvwe9WxQKbV7D88HC5H7EKAgYjT7AFg +Svl6vCRwj+DrCw3rkS6bUDrpOItp157n1KVaiQ1wnvi0oOcbI+MCAwEAAaAAMA0G +CSqGSIb3DQEBCwUAA4IBAQCYoAQUOBUab2qTkc7OHQU2LBBWgSbE8TZQYkqCL7HS +jm6g5Od9sQwKC9mA5BT+PNajnzJQ0CtHvkzALo+Ep+d9JQYtW7AJKbpCuewPe4GJ +4c3eLb7a2HQPnpMJP6ZL1h2qGraWs7+pBCi7hrZR1s/AfzpJ4uuAlhcCpvQK+FMX +AVMwSsu1PlBAFN7irX5qMga5MWUDEzgEJ27TMOov53OT/JU6LNHYizIpY+ZORsh2 +169EhaW3xfwtB45jIRnkQegmr8gPlqY9IEn2eH8dCKZrS41iKHiZ2IZbbu2wt8rp +/wxU7MCYm7tvw6O5oFQy04MRmEifloaBF9v8l5mNeWd7 +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/clientcerts/star.default.key b/components/relay/testdata/certs/clientcerts/star.default.key new file mode 100644 index 0000000..cc607dd --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.default.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsPNlUyblO8W8zEwivJAa7+5OXzjn2UKBMRfqmvsfnk4+Dn9k +znD+zrr6oLdN7LD3yL5fc8jLBSLOydWSbKVwsFEMtRj92ijdenUqRMKVqwLZd4Mg +qXvOU2AjKost9EXQKSN1azIVOk2Lk16Z7NENQFr2pe3oQrmopYvlI3eR6VNmTeyU +6F9fKv9raYQUx+oXpJina9kYF8X+HyalvTZMSK9pMg2FqV0jEOLQtpZdOpZ8l/hQ +8KIcW2rvw4bKwDct8U99G/B71bFAptXsPzwcLkfsQoCBiNPsAWBK+Xq8JHCP4OsL +DeuRLptQOuk4i2nXnufUpVqJDXCe+LSg5xsj4wIDAQABAoIBAFP0+BNu9f7ZQE4S +pbygBxfmEutdPaw4wMGBXn/wPL1I1GreiP7mYA/IaZgNwPxXrwlhQjrWf3VNYZ4k +JlhEWuZdXlEirXdwQj2TD94WERtVu8bXwYyIwImQqL7L0J8A7NZIC7MzzRyfpGTk +0tWpDseFIVfzPGtEWb9KVAry9ZklfUlj+sLiRBmFfDOJ6k+k7bSBG5BygQ7+OdSv +UEhXpqM7l3mJKnTHBtnWNYJphQ/NNhWyilL5e5DW13VlRdWbWjLyHP49YWoqkW/3 +xo5ETI5GXDSLaEtJZzaBZ0iJ+Wq6jHusVt1htvUWgRNyTJtvVaiB8S4AFBM/79IO +LSIPCQECgYEA55NxU3AHYG34AekzjbzL/OPVEYstA0PB9LyNazbgptHm+lMT1yc6 +7JC+Ggmrx+kgC8MsN9UOPaHWdrNeeDuBywLsqfoJ+9/h8q1RvcVcaiRzXgahgtB7 +tluEfCx5BOd888L3bLyP0LNPVR+kDk9qPokRC2S9DSP4Nz6iZc5UF4ECgYEAw50S +U7cWADShebYjzth5t3QE4SdzBg3sVfWgp6dfm7WD58cBF17TyB/C2NYru+k9rzyk +HodnluHvGPWTbfvvVsbuhR18CSGVevzGWDMhWHXe17qrDBFrCLBqkSENuDhh9gSJ +0FfCP3VQ4uTn2oSczZ7Nwd421h/sFYv3pT2kjWMCgYBK1EkFVij8DuzZ6TogbcZY +pGJANgHIkUeGRrtHzsbPAPqrHtdFacbPlIHpNgDNK1aQlnOeYvkkDzyWxKUdItVw +/TPuRP6e5hxD55+HO9P99GNXGbts4uNm7LGl0lUUsZydw/1hpJiwLpDPB9diTGUf +afkY/mdcbKgTaejeDsr7gQKBgFldRKYiGlb1tF9aKnmww8pfomsKDq5RZgrPMBnn +iPIltqEIdSMsGyxt9iIBlfE+2QNZfNDFozl29jg82KGCWxEVyJnfom65fQS2vhZF +JZK4Jpn/mWzl+DP8BHNQ2meyum4eHKelsAQ1oEJHuPRHaSs2xWEwF3UzGDOvwe8n +filpAoGBAL69XLGVL2StuZYFXJvIclEnOrcafCtQqpu50dZ0DxXczR8OG846rttj +3v9tcxGMaXrnBu/vOYGsvO6HuUU6ooidms1OrWnOyZTFxxH1xwv7xGlFPSb5mMAn +FvmSU/8uwkgOuIrxKGNxKKhbNM4pYINfCe9ODhGf5qaG87zy7Ofy +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.crt b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.crt new file mode 100644 index 0000000..c87989d --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEOzCCAiMCCQD0kF4yo85tSjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExODE1MTU1 +MVoXDTMwMDExNTE1MTU1MVowYjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEbMBkGA1UE +AwwSbmFtZXNwYWNlLWFkbWluLXNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAwPyZqt6HNI1fCzhSNHvZP1ymqJJbYMyKTkVOvlIkEBMe4oWzvMZS6CUl +q54j00SSF8bTcwLj1o5MgREzPxoqK7zCGWknkwcP7k8wIQxpA2MTWBkBlQMPVfx7 +8KojIGtjP30rBAYoYbnuZS53rV7aJl1GrOfgi0FIB49iLw20iTZARk35ntR51nRs +s1iWD9+6+gB+kqDo4amTQDRs6TU8ltqE5bSSn1tXXn8jPP3oQX3Hkmb6ck71ztPb +VUoh2lfu04UGmJ0+2JJDEchdRWrdUmJmASLtZF7uRtdkEQfhJiULFZsLvzNbyrQZ +5WYLotpj+SYMam9YQi/Kh8heeXgKPQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCP +nMyXeFZ4pU0kdMClj3IUhr8NQfHBUVWtyy8tffWT4b7xLaUtTeKXrzSAuTGt9AFa +Eww4cO8leEt8ZqpJYoqoPsrGgxCFUannmsQoKYnHCT/+zo9KAY2I7rE4PffFcrFU +VmlSDgeeV7pybCXXIpVV2SU+264O96Eyykl13I1t2dMrFZ7F5oSU9wOYvLgfQw79 +qzwuE2EN1vM73pAhbnShjSOKE1DXnSaXNCsdbmcUcla/6W2przU1Xx/0TrNj4esT +Nsy5UYlf76XXQJisUryux0+htLPbUJgu5rcFj+dOakg3rI0+h1GeYSum1vhQGBeM +NwkSgA89Lwi5zaYPzc5SOx/IVfh341xtdJYdGF9DfB0ghVJ6Z/5BaQRW2H0jxIM6 +vrhr03387GO9l7R8q1gnoklqY6KkIHq43maqKTJ6ZV/sjV16Z9yn63+f6SViFCzF +bkXRZdypSqc8vOP8f2q/9Gbc5jPyIMVtJtDZlyHRQ93yashf6BFcP13iWutkdNbo +fZYt1ZzC0Kwn5SQDnKGmNEzPyl4/nW0RgDNP6hYyxcL9JVzN0MSyjwajNwhDbxpN +FKXnwhk1MMclGAcGnDZRoYbweuJS3x8ZoAP/pTrxMSGw60iwc7co54Ina3c+xHAW +Dd8ac41yljTr1Z0o2ppiE46ozcxT9sU0pKAO9bAdnQ== +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.csr b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.csr new file mode 100644 index 0000000..466e8da --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpzCCAY8CAQAwYjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEbMBkGA1UEAwwSbmFt +ZXNwYWNlLWFkbWluLXNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +wPyZqt6HNI1fCzhSNHvZP1ymqJJbYMyKTkVOvlIkEBMe4oWzvMZS6CUlq54j00SS +F8bTcwLj1o5MgREzPxoqK7zCGWknkwcP7k8wIQxpA2MTWBkBlQMPVfx78KojIGtj +P30rBAYoYbnuZS53rV7aJl1GrOfgi0FIB49iLw20iTZARk35ntR51nRss1iWD9+6 ++gB+kqDo4amTQDRs6TU8ltqE5bSSn1tXXn8jPP3oQX3Hkmb6ck71ztPbVUoh2lfu +04UGmJ0+2JJDEchdRWrdUmJmASLtZF7uRtdkEQfhJiULFZsLvzNbyrQZ5WYLotpj ++SYMam9YQi/Kh8heeXgKPQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAI9SY580 +Th9Q4H3rUBMhdzVOW2f6Oyb4TPk0VBrVpOq2Puu50YW64UknAFz0beV5EkcjUjEo +Gib9SKLnDuQnJk90IbjivfAqrimT0bradbHXVoteuulftRzjhFBAnwrDD5Y1eSiU +mbKRDcp9IVPDFBhJrDgZhOoO/d7EeLETudXHNTTknxn4a9olYuAHupq+bsTif7A8 +DRWmCPC5BsNNK5w7nOC9ZcKCSvYbo0gLHzejivJl/GomYWpAXR8gV9xuJ5e3tVPx +5CcNRCYyepRiOYwjn0nUTJpRmRHjzm2vCx2s0Z9y8cOtwHBgEWLVrpCkWm+ik1as +EqfByEDkI4TVdKw= +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.key b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.key new file mode 100644 index 0000000..197369e --- /dev/null +++ b/components/relay/testdata/certs/clientcerts/star.namespace-admin-sa.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwPyZqt6HNI1fCzhSNHvZP1ymqJJbYMyKTkVOvlIkEBMe4oWz +vMZS6CUlq54j00SSF8bTcwLj1o5MgREzPxoqK7zCGWknkwcP7k8wIQxpA2MTWBkB +lQMPVfx78KojIGtjP30rBAYoYbnuZS53rV7aJl1GrOfgi0FIB49iLw20iTZARk35 +ntR51nRss1iWD9+6+gB+kqDo4amTQDRs6TU8ltqE5bSSn1tXXn8jPP3oQX3Hkmb6 +ck71ztPbVUoh2lfu04UGmJ0+2JJDEchdRWrdUmJmASLtZF7uRtdkEQfhJiULFZsL +vzNbyrQZ5WYLotpj+SYMam9YQi/Kh8heeXgKPQIDAQABAoIBAC9o3sdfS4/ZexPi +ji9ejYxHUpnu+ovZM9Durd2LiFh6mgfEGNezUQK+vXZfryVjnxBARAnpF9fJtXmv +pAtIfjuob3aWoqY4Ae3lfklgJxwx5rqpyf21drsE/92rrl10hDlAoqSmbp0zdCFf +PP9WYfkyyP4JKNyM+QkCbHhqwYNDZrZIJs7BATmsNoDQ0kieFYCvJlaZ3HRy9fpw +/HIWVtJryBNbZJXLaN58lbOoJRK9h3ipsqw8NdqDDtOQsJ1lPbQwO/+FvdW3v8Cz +1R4bGHls2ESESiaGbP9VgSt5nC0whmLyKePOHsq+xLU+k5kIPqRADzMCATE6OEhI +8R/q/gECgYEA+OMypJwZyMIHY/FQdN/bDnEluS6zdpLW4CLJiZxCbKThYzKJxxWx +RV20Pg0i2tCOLM2SpyvmwzVNgF/XeFxra1DKty/8K9aiibPkCbbruzRrgjSVLz0m +tynVjSGx6xR905aRg0ZZlnMLW5Oaag1FxpcXBW/WrqTmA7+BNE7dCJ0CgYEAxoBy +EYu7ZETePmwNS7b6FQ2VJFIt5A5W5AC/0J2+MtXBgNzmGvVX6ss3VaHdlcJORgP3 +03eGoUnRANsfyQFsn5moTsbL+/33xzmU26KXc7X0EH1sX2eDNdAy/w1wbOXBnlmR +DoPKRi/yRs9r9axKA6XhLbpGa95c5Zb3cslrRiECgYBb2a0E5r71QfXsQlBI8Zws +FkGvt7ZvWf2Gtg/egxZYdfjf3PTPzgef69bq13o3eqQdkLhAjYkaHX++kqA4QRM7 +hZNqS4EqxIkzfHn5w9H3Qn8yjClKR60UB5JSBU9dzDArd/O3mKzTQx277BsebEuF +4JiUXxPBXAWu+Gu8XCZiWQKBgQCyGKw/cc8/t0pvVyLWu+TE3trhK5pg8zyidMmk +suLjWUW0CH2r0lR/JLUQ75mPtDXmO8kuwh7YtxH+YpuyDMUYrRSFGXgLL2IQLWmR +Zd/cwXOpF98T+huPPK2EiNpF2h5NpK6xBy2Eq6AESDXkxh/iBgJytJ5aSbevhjOd +mXnWAQKBgBoJ2dsi12LRCaqwmnnnANKjlBnNzCzzSpcNOHte6SxDuI5x4PO1ZHa3 +JlCzAeLGYiwjQB9NXW37JDLq2FhREU3szpS6ew2+NHZdmHRZcETysC+eCyWOsZmd +EDIqe7EdJUbHXP4MS03r6b5ROc6vRfZO15R4cGufeDohrKrYf/8Q +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/gencert.sh b/components/relay/testdata/certs/gencert.sh new file mode 100644 index 0000000..5563667 --- /dev/null +++ b/components/relay/testdata/certs/gencert.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Script to generate test certs using the custom rootCA in the code +# Pass the name of the cert as the argument. +KEY="star.$1.key" +CSR="star.$1.csr" +CRT="star.$1.crt" +echo "***********************************" +echo "When prompted for CN please use $1 " +echo "***********************************" + +openssl genrsa -out $KEY 2048 +openssl req -new -sha256 -key $KEY -out $CSR +openssl x509 -req -in $CSR -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out $CRT -days 3650 -sha256 + diff --git a/components/relay/testdata/certs/rootCA.crt b/components/relay/testdata/certs/rootCA.crt new file mode 100644 index 0000000..1864f5f --- /dev/null +++ b/components/relay/testdata/certs/rootCA.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFNjCCAx4CCQCUC+ovW15jKzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDEwOTE4MjQz +OVoXDTMwMDEwNjE4MjQzOVowXTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMw +EQYDVQQHDApQbGVhc2FudG9uMQ4wDAYDVQQKDAVSYWZheTEMMAoGA1UECwwDRGV2 +MQ4wDAYDVQQDDAVSYWZheTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALfQiTsC6u3g99OgQ6TH0t4cR66Hapaouyh9p+8J2iUMPIkZKw9Z1iyKd+n5zqBV +lPZl2t9E821A15MjMCJOKjT3F9wuNeiBvxDTAmpyK+1Nj5OXXEbVXgVsq2uy9I+Q +f5nX47tXiiqrmSLH1cFYWGzs7RCuK4Df8MykKkKYCIW/PAEReHFqjVlUwFaff35e +T1b/NcGIyQfNw3MRcLRtn3BF/LvwraqV4MothqCYQoEVXfp7ON5lOvaQaPLg++F/ +I/U2aVI9iQ47glBY2t+lVXPX8TwwMFNu3k+xnJ0+NRDlkXoyDLTfLNcDKxf1wCCl +NWw2DvtVMAyDtibsMUeXR/q3kl3UhTNupHxf3YNMIhn5NpBRHi9JEvJbe9hF2O1f +NvIaJeafsKvT1Co5sYxokVM0vjns31qi7G5f7PvPxmHg64srV0aeXRpdOb4kLzPD +Vlep6kmJnfCoDjNLXUQeZRrjJgU7pnba+2QVsXwaK4FUPe+fhrO2OFaDeZ78MY4z +2QsC7rinspkvl/mwoxfyB6g/8IS3zI6Ii2LRnnqjpqw4sphnxz2gDAhnEo7Nc9A2 +Cy4I8g04Lqn4O64WP/FFUEheBWIpKPIFEzWtZvguR2zETvB+H0o3gDDfgKM5odsd +06Ujh+C1pouMfyuTTDdNWrTDpNa9MPBsKRMM7rvm9RcnAgMBAAEwDQYJKoZIhvcN +AQELBQADggIBABUBeOADlWbNz+FwjC4TtX5+0td4pt3sPJjS4tREYZ5lrWYlByG4 +To9Pl5AMfd7Bc9r/5tkDyk0mIvawpPBDJL8G+cAJr6SOn6Pp4puLXCoyew2U9UiX +De+SPEeTKi/YuZjgZIO7xReat75w5ib8Zvx2GyyFKhS5fxxCtWVq2gI/UFD4j0wn +OXwE5GUkmQRSIFcmzmuPhGwDOO2VGsd1UJF/trvH8x0wOhiJsCjtvXSnVad+Dub+ +EXGeKYCJMlfnplqERXq0mkakUT1kA6qdJ9m1cvu0bbCap8jnrfuRDkGQMkB/1UHt +c7YRBG5y0cwa47tMlKZ1/lXkwYjMPGWqOshtm3lpIvmv+/CyxaxTdN6cs14uJpy/ +QsOmOkNuaZv+1DSwaL3bQYLHYXhm1TdcfFVZVsTVTO3ZfZH2LqORpqTjVe7JCGkZ +09kliztztVNtT/4C+CIhY6i2zncU1lCFQkUZh9e4uyNR8Y+EjMcivMnaAVoCK8lQ +Shj0uPSEkuYrxHFIJ9AQuTrctI53u9jBdOkjXk00NB2gwLyDRhWRUHz6KxpNMwpc +/8jMdU/GjESFASGMPuazMns0g4NBqE4jT6tGJhL4sFmn7HOHF3g8uYZnJmTyQCny +FQOQw3/KbgGNONiTpYnmUrlCvtZKC85awJC07G/YNxAZM0MPxo+pqgz/ +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/rootCA.key b/components/relay/testdata/certs/rootCA.key new file mode 100644 index 0000000..064d2c3 --- /dev/null +++ b/components/relay/testdata/certs/rootCA.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAt9CJOwLq7eD306BDpMfS3hxHrodqlqi7KH2n7wnaJQw8iRkr +D1nWLIp36fnOoFWU9mXa30TzbUDXkyMwIk4qNPcX3C416IG/ENMCanIr7U2Pk5dc +RtVeBWyra7L0j5B/mdfju1eKKquZIsfVwVhYbOztEK4rgN/wzKQqQpgIhb88ARF4 +cWqNWVTAVp9/fl5PVv81wYjJB83DcxFwtG2fcEX8u/CtqpXgyi2GoJhCgRVd+ns4 +3mU69pBo8uD74X8j9TZpUj2JDjuCUFja36VVc9fxPDAwU27eT7GcnT41EOWRejIM +tN8s1wMrF/XAIKU1bDYO+1UwDIO2JuwxR5dH+reSXdSFM26kfF/dg0wiGfk2kFEe +L0kS8lt72EXY7V828hol5p+wq9PUKjmxjGiRUzS+OezfWqLsbl/s+8/GYeDriytX +Rp5dGl05viQvM8NWV6nqSYmd8KgOM0tdRB5lGuMmBTumdtr7ZBWxfBorgVQ975+G +s7Y4VoN5nvwxjjPZCwLuuKeymS+X+bCjF/IHqD/whLfMjoiLYtGeeqOmrDiymGfH +PaAMCGcSjs1z0DYLLgjyDTguqfg7rhY/8UVQSF4FYiko8gUTNa1m+C5HbMRO8H4f +SjeAMN+Aozmh2x3TpSOH4LWmi4x/K5NMN01atMOk1r0w8GwpEwzuu+b1FycCAwEA +AQKCAgA643BTm4ewVhaPkhSZwfrodZ7xZyjaw4vSWQ9oIXvlypnKQmntLRbUhjgM +mZ989m4W0mLgjLKAGfn2hZtyIiCbf14BiDx1FLwlo7Rubc1s5SBOZHuHpevNSRdS +Fa8MH3sByoyHBHCbYwZYgjUVEDykCPOaGHKPgy8eIKn5U8PHNZD/tp0jmMNYDzLf +jZHG5mf8sx6BtlTRTzaCst+srkIaMhHaFC1PLV0q6JLs+Fq2CCZnjPqXJU9SlslI +ppOLlrPqZ1gupNA34xDItO7KH6nUxyXd5oJU+Hv4hCpspfVSSHnm8AnqGGXBLJ6t +ummnC8aluFvsYUpXeArvRVQ0X18LhJNm1l+hADX+9LDiowM2vKbIBHPE2lcvmnPa +Wknqa/rjekjHn0ZmSbguOKtvWuNO9D7Q1eeWLIDa1WK4EZFgIoasXaqvG3qjXUu2 +ftBUHRddtbmN9YcxWeCCciWdzVi1SQnsIKdiLaHJJ4j4EGdVTvw7aVCsmDSNouFN +DrH+ncBNFJPQCalAol27kPA07aO+NJUFMsfHR/JYNCyZGMVpyvutRDUmoBLNTVEy +JZJmxrhDIOFbBjPvmhgbPN6igzZPbtcabVGJ5z4T0mg+xUdgOINfxgd/vP3i0uJe +N4ebYH8Thbov5xVL6y8UI3Aj7uJYFZ3wIbEzQ6VfYx47ilExoQKCAQEA4W5daXLp +reM3tA0WVpIucRUfQ8INH6V9aQNJodl9PrIaZgOy1P3yPgyWYOxBq/VmxCJevCZX +dwd87w/D6pb+J2mUl4rHGAP1Z9NyRjGhZR2zd2l15GA2G6OF2dOol8dPg2Vy52XT ++4P8NO2TjkZBbxdgaY7zoLdqkhFQGHQG3yVHWXxDclxvvaw1UQ/Jsqhjh76DLFP4 +8Wgh+eKdUWVcfrPLPxtCD1AqYlKMUUPTM1bMtndMzTKhLvHn7Ps7bYcg4ZljftXy +22TDBwKDyFKY12A1+08IakJTkE+x0AT8udDucrhCcC4UDHovaMMG79JDjBzPExFR +Td9SP4MnG/AuewKCAQEA0L194ofiET1aMfxT8NuK92hxvoxaLJjjZNiT7jlrFJpO ++9L5gnF/sTUqyIJ5oFaMajK9uUbjB7fKAaWZ+HscRqqBbL6jP2njEcuxuCiNYEan +Qa4XG3LOMLRE8XavVu+yrKyPqf+KeZV2sDZIHwupEPol/hMvavxKUnmfaTCc15GK +ikV7uciNFtYW0BfkS0Od94KBbOEsZO6tFYRlbM4IqX4j06JXGA54CVpUC6pmV7l6 +heDhMpGkcG49GizeZeUd3TOmCv03ulZH66WMw+mLHInEyNuZY4jgeFbsH0NjDAvV +aB7/dPWHP0/0YX9kwezTcUzyWyWPWtqqBTHaPIywRQKCAQAWXKFNhYWipyhuZuHx +YnzS4NqCGSy+F+dx1E6ojG99GaO+vN0vfaVa/FqqcKUvDp/MEvC8zzzb5NVCzZiM +m7/IXxPLWIvZWQMt8m6igefVpPWZgc+dAOI9KaJsci+GJtI/YX/dbl9x0qzg/zQk +59kz0OjSb8jBB5C9vpPoaRIJbqQ+PzJdlMfM09lSGjPS0htVatOOQSIvqDm+lPkw +CtL7CpC9yNFrvXMG39NleEnqDNEvxHPM4xnUE1EDVASYH9BmMJYLDeQSWVNzTY+O +/oqEytlv/W4DRGMPrKghz2zWbeBJZvfIMvnwJuqvBLGadowWyQDNjmk3RVDeVf86 +KUAjAoIBABo5A9x2Z4703yTSZxUl72I/S7K9XQKnPkHH92WA7xPKgsud6Fxbt8fi +PvqrcW4UEVWyBCWHYzPwAb+NNzeA979k4hdwJ3/q5Gyy4yOYbJJArfhRT3PSS1bQ +/qw61bIdMJwCnZWklZfKj/t5BjxoMZgBZM5zSIyvHwMXZlBHnBx0myvdqLphGGBb +Csifh71wxCJ40ZuE+he/6ByA4mD1zR7FyuhmR3ytLKU4JMDGak7w7RRueLvqkfbR +0nk8U8AHNWY5e817nAmblzNiO7nM1/4AzKjS9YUMh+52NPmEdKfpwKhw5HSj95ab +i77HyzeVccaU/Qk4QbcPKza12Y2ruuUCggEBAM4V3ZYWogxPyx84qPJOtbnnF6Lp +QokJoXtGMDpVZ5kr5JqYFniJ0O6r1Uaa/GrehzRVE+AxIDZuJSGSrhDvbG4YjbU4 +PHjWOkhuUstSK9zd0Vho2LeNK4GUcNMhJeFsPFhfhPeRzlLF9QisHs6l9uiRqiPy +/U3qOEjrPlN8Tllv9upkkJDmgb42VPLYJQ0kNYf+jDQEcipt0Ikfodq+yqveneBZ +8gITjd7KnCcW/lJyUdGylps06AtYFu1G2M1D51cJYc38zhnJVgZ75PSlrtT4imOo +GI6AoFe3I2WZ3HOLVmfKZ22t+lBwQ2iZ3vXCegy9o/ONUpaJO5UQlknFzEE= +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/rootCA.srl b/components/relay/testdata/certs/rootCA.srl new file mode 100644 index 0000000..c1b3a51 --- /dev/null +++ b/components/relay/testdata/certs/rootCA.srl @@ -0,0 +1 @@ +F4905E32A3CE6D52 diff --git a/components/relay/testdata/certs/run.sh b/components/relay/testdata/certs/run.sh new file mode 100644 index 0000000..1f035ff --- /dev/null +++ b/components/relay/testdata/certs/run.sh @@ -0,0 +1,53 @@ + +## For refernce only +### These are the commands to generate certificates. Also refer gencert.sh script + +# ROOT CA +openssl genrsa -out rootCA.key 4096 +openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt + +# Generate relay.rafay.dev certs +echo "User star.kubectl.relay.rafay.dev.key as CN *.kubectl.relay.rafay.dev.key" +openssl genrsa -out star.kubectl.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key star.kubectl.relay.rafay.dev.key -out star.kubectl.relay.rafay.dev.csr +openssl x509 -req -in star.kubectl.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out star.kubectl.relay.rafay.dev.crt -days 3650 -sha256 + +# Generate relay.rafay.dev certs +echo "User star.kubectldialin.relay.rafay.dev.key as CN *.kubectldialin.relay.rafay.dev.key" +openssl genrsa -out star.kubectldialin.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key star.kubectldialin.relay.rafay.dev.key -out star.kubectldialin.relay.rafay.dev.csr +openssl x509 -req -in star.kubectldialin.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out star.kubectldialin.relay.rafay.dev.crt -days 3650 -sha256 + + +# Generate relay.rafay.dev certs +echo "User star.kubeweb.relay.rafay.dev.key as CN *.kubeweb.relay.rafay.dev.key" +openssl genrsa -out star.kubeweb.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key star.kubeweb.relay.rafay.dev.key -out star.kubeweb.relay.rafay.dev.csr +openssl x509 -req -in star.kubeweb.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out star.kubeweb.relay.rafay.dev.crt -days 3650 -sha256 + +# Generate relay.rafay.dev certs +echo "User star.kubewebdialin.relay.rafay.dev.key as CN *.kubewebdialin.relay.rafay.dev.key" +openssl genrsa -out star.kubewebdialin.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key star.kubewebdialin.relay.rafay.dev.key -out star.kubewebdialin.relay.rafay.dev.csr +openssl x509 -req -in star.kubewebdialin.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out star.kubewebdialin.relay.rafay.dev.crt -days 3650 -sha256 + +# Generate relayserver1 client certs +echo "User relayserver1-ABCD-123456 as CN" +openssl genrsa -out relayserver1-ABCD-123456.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key relayserver1-ABCD-123456.relay.rafay.dev.key -out relayserver1-ABCD-123456.relay.rafay.dev.csr +openssl x509 -req -in relayserver1-ABCD-123456.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out relayserver1-ABCD-123456.relay.rafay.dev.crt -days 3650 -sha256 + + +# Generate relayserver2 client certs +echo "User relayserver2-ABCD-123456 as CN" +openssl genrsa -out relayserver2-ABCD-123456.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key relayserver2-ABCD-123456.relay.rafay.dev.key -out relayserver2-ABCD-123456.relay.rafay.dev.csr +openssl x509 -req -in relayserver2-ABCD-123456.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out relayserver2-ABCD-123456.relay.rafay.dev.crt -days 3650 -sha256 + + +# Generate relayclient1 client certs +echo "User relayclient1-ABCD-123456 as CN" +openssl genrsa -out relayclient1-ABCD-123456.relay.rafay.dev.key 2048 +openssl req -new -sha256 -key relayclient1-ABCD-123456.relay.rafay.dev.key -out relayclient1-ABCD-123456.relay.rafay.dev.csr +openssl x509 -req -in relayclient1-ABCD-123456.relay.rafay.dev.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out relayclient1-ABCD-123456.relay.rafay.dev.crt -days 3650 -sha256 + diff --git a/components/relay/testdata/certs/servercerts/.srl b/components/relay/testdata/certs/servercerts/.srl new file mode 100644 index 0000000..b7ce295 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/.srl @@ -0,0 +1 @@ +CB6AAE2A400B8C7E diff --git a/components/relay/testdata/certs/servercerts/relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/relay.rafay.dev.crt new file mode 100644 index 0000000..1e1158e --- /dev/null +++ b/components/relay/testdata/certs/servercerts/relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQDCCAigCCQD0kF4yo85tQjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDEwOTE5MDE0 +MVoXDTMwMDEwNjE5MDE0MVowZzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMw +EQYDVQQHDApQbGVhc2FudG9uMQ4wDAYDVQQKDAVSYWZheTEMMAoGA1UECwwDRGV2 +MRgwFgYDVQQDDA9yZWxheS5yYWZheS5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC3xFSCk12MhfqrREW1UpGHbnFQ3Icl65TN+H99rueD7eL0CXf4 +bRp4V3ncQk35HHVBn6FaSbplXfNNFq9YSxfT+GGW07AHkUryubVQSrjH4GfCn9gW +ngxM1cslAiJNULmkFG9y6xIK56auQl8xbf1qGKjuAXh6c5AKmCIx4Zyq26ucdvTY +a0kOmBEjdMFA/5hiAvOsAlUt8fSC6TnJ4eL4XZNrRpbbTVyJ3m4yFmWv1Az1hTRJ +ZYbTFiOEgPBi/FxUSkaCt7rsY+JSjKIsYJH3/Lo4kvH1X+9QuA/r7ZCzx+mKxCEi +qRddbintLMqCkuYCH12XVAmLeElCLEOqOUk3AgMBAAEwDQYJKoZIhvcNAQELBQAD +ggIBAF89TL54hR3mvAiegldIajkVW2QYFCkbKhH1/E2m5jEzdA0dqJk9lJgtIJGb +vXBGopVRDad6lBqyOfC/E0QrwXt36fEUQZtU8KIc+21XzcICPcmh9ro5LecDmo+e +CME7xuzbpYBH9s8jWL5FykU7UlZ2ETg1BTj8kHIPRV3WkVHb34/M2CREOdnikKfF +oU3R+PmoOjOb/zVSbc9cPe9Wa75cp9moortM7SzE+be7Ah8uTYlydleRYlQ96PTM +Sd4Vx8qMyRgEYgQTzMhMeba9XTtJpeojmdWM5GsR+TBPhqhGSrDY1qcfMYj2Vxrf +YusIfB/hlnrk6s0cS5b0EoIstCi8/Xo++rU0HcU8GSSzU3+wvgcgfH9v+qeFK1XF +zr3cKsakdvwwdllk1Ko8nIttpPXfG4h2vyt82hwbesz7qJ/LcUtTUkDKa9ap+r+0 +bioO1YIio6bVwWd4e4i6HekKVv/+Jbrq516cJVpYsmkviRQsbIqmPMxAjTuFnJ2w +GPF49oUbbByUeEqsRb0R/vVEeuJek01QRLkiBhwrZySBzfhpoNprXljmPMw1w8zh +YMGsEp57a9HC5A6kYL7d8fnw8xjDqcv7FU4VFtwYqm3CLJRhQTMr5+3EnH4xJel2 +z7F85OoCigjIN7a4iU//JeZZB/tfhjaot2g+5gWWKFG2EivL +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/relay.rafay.dev.csr new file mode 100644 index 0000000..4e0c3c1 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICrDCCAZQCAQAwZzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRMwEQYDVQQH +DApQbGVhc2FudG9uMQ4wDAYDVQQKDAVSYWZheTEMMAoGA1UECwwDRGV2MRgwFgYD +VQQDDA9yZWxheS5yYWZheS5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC3xFSCk12MhfqrREW1UpGHbnFQ3Icl65TN+H99rueD7eL0CXf4bRp4V3nc +Qk35HHVBn6FaSbplXfNNFq9YSxfT+GGW07AHkUryubVQSrjH4GfCn9gWngxM1csl +AiJNULmkFG9y6xIK56auQl8xbf1qGKjuAXh6c5AKmCIx4Zyq26ucdvTYa0kOmBEj +dMFA/5hiAvOsAlUt8fSC6TnJ4eL4XZNrRpbbTVyJ3m4yFmWv1Az1hTRJZYbTFiOE +gPBi/FxUSkaCt7rsY+JSjKIsYJH3/Lo4kvH1X+9QuA/r7ZCzx+mKxCEiqRddbint +LMqCkuYCH12XVAmLeElCLEOqOUk3AgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEA +DWm2eXew675cXj5pdsL4mKLnpQjSy+3Ewdg+NRQw8VUF12xjxVNUCPcrX2wcrHx6 +1KqCxOKr5b3d1SlqAKVwEh+6J5+A/nbfFSxT2gUmqy1IEe+obYSvj6AiN9D528rw +e7MKlwBi6ARgyIT3HjC95gKehX6qrifNiY0aNnP/BfaCXNxPfP8Ltyfqswv9+P26 +kHePUcVBLOA6+SKimVvYqhAtLhaqjpjBo4JeX+MB/tP+kzAb06wBQOb84SILOBEy +aOMSF1uR0peB92zxLqblhJMoyZc/rOH6zE3PHjNPiPcX9BFJJ8LHGLXPAxoR44SX +flK/mNcmRPVplIyJ57OB2w== +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/relay.rafay.dev.key new file mode 100644 index 0000000..9f86b6a --- /dev/null +++ b/components/relay/testdata/certs/servercerts/relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAt8RUgpNdjIX6q0RFtVKRh25xUNyHJeuUzfh/fa7ng+3i9Al3 ++G0aeFd53EJN+Rx1QZ+hWkm6ZV3zTRavWEsX0/hhltOwB5FK8rm1UEq4x+Bnwp/Y +Fp4MTNXLJQIiTVC5pBRvcusSCuemrkJfMW39ahio7gF4enOQCpgiMeGcqturnHb0 +2GtJDpgRI3TBQP+YYgLzrAJVLfH0guk5yeHi+F2Ta0aW201cid5uMhZlr9QM9YU0 +SWWG0xYjhIDwYvxcVEpGgre67GPiUoyiLGCR9/y6OJLx9V/vULgP6+2Qs8fpisQh +IqkXXW4p7SzKgpLmAh9dl1QJi3hJQixDqjlJNwIDAQABAoIBAQCn/dOf1qWzW5tu +y6fJ/aRKIV13pVHrCXRr4lpyA91jDqnlm25P7TVAg/9Dql1reGd8yTgQuMur9PoB +I9bBqUwsG6ji37vQ/zJ7t+cXcmd/WRahpcw9yNfHub3OpCfFdCFw7LmBXYGN9Hy8 +WrJ5FmqOfeKYSUHg9xnbhX3lCbFVwpI4KE7WXuvZH+7A+hJl6UFXm0nMGDWscylI +5MUS6QPoQZ0eMR2I3XgX9MrspdSzGdg8mTPIfyK3Di1RfbJEXba7HqlVk04r1PtP +LXQfftRpZVrpRt1CpzHoJy0sxl/iuqWtWSHJ0KxvyxZpaZMOmjPiAkvQ9cw5hA84 +POWfLXLBAoGBAPG20UqKH1pbeFN6Ne5XCYQJbqDw5nbreDHcfZp8cZ8NClf5Yn15 +XRax7eK54awYyW5sR64dTqT66lUncO1MKItbSiuIJ8Yji50oObYD/r3jLhmTXtei +I5sNIrOM/eTEaQtKgX6wUHnKrsaDExP1AcOXQB3z1S1xiog2HxGD8Z7hAoGBAMKg +wn/gU9y3/NOf724KiS00+nEmFnjuW6MFZFhx0qKLNLeSfjgUdd5WMdDCO4fGjhKT +gajpvQ73btFrjnfpsRW8DHCKV4lZCWV0q8Lhd7GdP9xf/Yete93I712yGFfxxQ0T +Gp8eQ6atUJ5McQryx84zLUzPrQDPMDnguiAAc2MXAoGAUNlmnfkfTZjk4X8yRNuG +Ck0hXOHXJJXUaVEoDiFr29h/yjdXU0Lh+TJgsIKeUnMkBTRgsYOKB7MJ6Ju9sRHV +Se+qGm97ZlIDY/jNfO1PKvKvO8zIX3YTVMyK++XAEuIDhMxSDi8pa+N4nFvBaN59 +zY9s2dfySTX+MVtw/unUSuECgYEAri5pY0BETVhr8IlGIllTgx3StMi9e+T2JeOB +dpYHSGkE+PUl+NJ22lCRrJ7n4A+WvpE8jQsCpA5VIx77Fd4TGbx99YoZ5VXFzwKn +ZXWbRV3R9UwC/hYIsOhPMK9ekfZRv+3E+XqAWP7rE4Cd+opkvumWNRP3ZK4IHwQ8 +Dr/YhGkCgYBTWyOAS2D/xa3lgFbsErh+k+BgvyfwvLfLmEugwqioiWn59oUvA6At +MCiB1eTbq8CzUZGElZCH1vQjzvL475iSZtXHD7qN3OHp6iUzF0Sw0SV/ptwMnQXN +mMQtu9hlonPCwklIfHVvhTAu+eaYFpIkZnrbwxlcEcLeFk8EZM9VRg== +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.crt new file mode 100644 index 0000000..a78144a --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQjCCAioCCQD0kF4yo85tRzANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExNTAwMDMw +N1oXDTMwMDExMjAwMDMwN1owaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEiMCAGA1UE +AwwZKi5rdWJlY3RsLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAO7cbcZP2XZZ6djdO+RyjH4NOpqaxURgLPosXVYr2mTduizA +jtxAS4sZPfdZk9wQY7RT7hVoOQY836kjiiyjwuy2BhhiYjc4ovzGR25bNwi3I6bW +QN8EHF5KIySB2gXgn9br5LgDz6chZwcWYfyqWcbJQ2oesYQIeOCsSkm1cNA1U6J5 +gAu6t8Pe9nbaLiH2t38QnVmRbCoNYUpzUNQ5shrM+AfJ8CRZtahkd8ucBPly4T/9 +dbjIjV+f8KqRTfbT932KkKLREyOhugfuFJ//YH5XETr38EcdHJ0i9Hx9P+D7+Mou +X7o9+quX9qBDPkVSJZRREmwHw0y3ZkvK9VtRbqMCAwEAATANBgkqhkiG9w0BAQsF +AAOCAgEAAZ1vLleh+w+6SCNiMmuwYFyIQ1hEv5GlMR47Tz0P6iJdLAaCTU0zBihM +ChNjoy0C3OmzKmmlAv02r8GCzkDLAvcfh5T3QbF9fDDhAGKqoOZXFsUiYn1gx4o6 +8UgWI7Y6golreD7VWEa0FCctoi2TFsmWgAPtaY9EJKUsPpt+2Awtb1ZJ27xEcYfI +gNN7ZZe3KwropMiFjCdIT1Uy/99TDiv8z/w1RYH0WeMBQLATED3flYewyLg962z6 +tkTooS5tvbtpbxtrkjBsKkktHo8wxacVvcDO8VuOnZJZC4JCn5CFgBYkdBUBpQSo +R+bI+ldroxSgcUndNGwnNWJt7C47p1WUMohR7Uqf9uuCYFuy9tJMA9LN1nyRRKV5 +D2/N2pwias89+Rk4PNg03jAeJRoCQsV6zMMMd/Waryg04ujWUnBpLR3ZyOLL82sN +lIpTed3+2aR1F6dK/dE3n5AFNcZ3HTvPNpgYL96IjLxPyInMSBQ8tvzmIrESMRXt +ypBRFg8iU5Ri0crB/aHxmqerjDHbwvLLU62424uR24IJK+nRIkYdrthqaCFsCT6u +JcHi2dfy+s9aCjhSmu1BxIXgJCV6tItQ+dwGmRVMQnQC5VBqgPm3UUdTa+rs3pwo +0VYyQi1QDQWftJv2Y/rvtLv/OUws+aCk0AkjjohTo9kBkxsD1zI= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.csr new file mode 100644 index 0000000..97d47f6 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICrjCCAZYCAQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEiMCAGA1UEAwwZKi5r +dWJlY3RsLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAO7cbcZP2XZZ6djdO+RyjH4NOpqaxURgLPosXVYr2mTduizAjtxAS4sZ +PfdZk9wQY7RT7hVoOQY836kjiiyjwuy2BhhiYjc4ovzGR25bNwi3I6bWQN8EHF5K +IySB2gXgn9br5LgDz6chZwcWYfyqWcbJQ2oesYQIeOCsSkm1cNA1U6J5gAu6t8Pe +9nbaLiH2t38QnVmRbCoNYUpzUNQ5shrM+AfJ8CRZtahkd8ucBPly4T/9dbjIjV+f +8KqRTfbT932KkKLREyOhugfuFJ//YH5XETr38EcdHJ0i9Hx9P+D7+MouX7o9+quX +9qBDPkVSJZRREmwHw0y3ZkvK9VtRbqMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IB +AQAPkDdgYNELjqRgveMqBVBafxbuRAR4AdAzPDbCLOBDva5WiSkiOOKAhLBbspal +xkeOdAg0KkUhSX/J+l1yKpP7AnitpTEG/sJog7SXv4/Pk5oaPYwMKpHfqXKjcgBE +QrtPBgDmQbyQwTkcc4f77TvUb9dv+dXK8UGjcjcnDlKdnG6XdyGDFInLjfR8SE3/ +u2CQ7fKZIO3Z7G1IsI3YKlK3oVTWtquhQvL/YY8hhT80xMItHJ4GL9eFQYT7NFvE +xQHXsfLuIhlG50wT/Ce/YXmpndOX13FppfgkXUC7zpGtiecX+oOa2ybT+wkZ2kHP +/ouZqrH4LUzSK4ufip6Xip5B +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.key new file mode 100644 index 0000000..9d1a6c9 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA7txtxk/Zdlnp2N075HKMfg06mprFRGAs+ixdVivaZN26LMCO +3EBLixk991mT3BBjtFPuFWg5BjzfqSOKLKPC7LYGGGJiNzii/MZHbls3CLcjptZA +3wQcXkojJIHaBeCf1uvkuAPPpyFnBxZh/KpZxslDah6xhAh44KxKSbVw0DVTonmA +C7q3w972dtouIfa3fxCdWZFsKg1hSnNQ1DmyGsz4B8nwJFm1qGR3y5wE+XLhP/11 +uMiNX5/wqpFN9tP3fYqQotETI6G6B+4Un/9gflcROvfwRx0cnSL0fH0/4Pv4yi5f +uj36q5f2oEM+RVIllFESbAfDTLdmS8r1W1FuowIDAQABAoIBAGiWbBqha/6+b/5d +IJRu88Vd1YNMsYO2+Pqx4kyQ9xejXpmp1uPp4yoM620a8/sv0fhrp5wH/rAUh3pv +7ViOfBNZwGF88rRrQDKePzi7B+XxCtNKjS0Zh7Jhk/GwSd649EQPFh7czFfJ8GrS +CiWwEY9RIQueVOwTKZp21jBgGf2QA3jcedOgS36q2zRGU23uBFLGVMNsY+lXx3TZ +DqDwckWWLtUfI2PqU5tKhUUcBlSQYKk3WL2C6otVly56u4C+Q4TPE/PSq7sH4n1H +1q2GGhX3gmp92V+HZ4HJiuY4xXw4QRycKSfcGvIF1ZKGcA/Dl+STv3TvY8nfS0AW +18VFyYECgYEA+FSz4N6RzN3ndn9t6dp/gw0PX6HlrjXnW3rphOuJMkuj9LOinusD +K/xrnpIoPvfh/QXWuebqBhw0LTgGMA3yYW63nr+te5/N+A0VytcGD6Htp01IYzt9 +n1V7fAbB4CDFM/NLBEzNsbJT6308eVR9e53lvJxT+sizz24gFFc2VisCgYEA9jzb +ppkM8uafsxMHbNmduVO4hTagL6obKuGXCkR6X38m1R+k6/7oRvaRty9OarhaY/aQ +oKgDtgaKyVWxft5yEe1B6qczlRvhxKVTAd5py++Yg4gyaCvNrHGuWfzM5c7vo1Lz +k+puDC8kbkPSATEsPI15gGmVce8cc4s3wyWrxWkCgYAzRHlmYUzEeMOCaUht0Rgm +HBoiajAayTIcZsGzGVVXttoAeN2H5YjgGdq2AAKjDVXwgLYV5yBFZqk29KwCW77A +crUh5vyP2ywQm9L173Jm481GjuP06KFEtccqkdKQzPMpiH7iQmDs4J4GrfJAMjs9 +vBlj2PpVef31Qw0IZgDnxQKBgQD1SIw4+S6369nbZTI5UNFXojklodORh26Gma9+ +T9ZiuGNzW5FKcGLrBYUDqEEB9+A6wYsyLgt8UCoCArXLvkR87Q2DdMjiWKLd4x27 +t9VFi8cuBwbHVBMOGzPlefiVVzzJQcFXb+fthSxSFobkHxqEuxz0OLjghKKfymdi +omzoOQKBgQCn0i7HSy8nZsGbq3ApNYfit2hfcGK2cfihvMbSunx1myk4UDFjiExf +tKnycpLriTsKYQ+3Jj9zO8+QqjPxb19XkTAWKZ5HYQEwS19f+mPwMWyn8mvwOruL +FDk16um5sMM1kVwStMnlg7TxCZwrT3dXHPQuZj+qHcJnacrt8JBqEw== +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.crt new file mode 100644 index 0000000..f1b989d --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESDCCAjACCQD0kF4yo85tSDANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExNTAwMDQw +NloXDTMwMDExMjAwMDQwNlowbzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEoMCYGA1UE +AwwfKi5rdWJlY3RsZGlhbGluLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANU/MiB/kLKlcMhMtXuKIGu2riYUfmfUIz2MQNo+ +oz4wQHUGg9P1sO3KT+kzfOFN7EkAzwryCIT3GEKTYoVY5X+Cllow3+IhtovgHhhr +R9vSDBf6QSWqsVbai7U6jGQwtr9D+4Tv8O0vXtUWnTylE95/2Ecdf+n/7US435Rm +9eg/CCsdgPw/m/WTPA9mU7xrpou5Pwbb+TiCJsnQKbEGGuFilBdBLNZkj0qOAUMl +YBu7A7n2yIljC5zJ93dlLX0kdssSiDwt0KcXwrWs7vhXxrnTRx6sEneloa10pbG2 +0BPoqleGKk6LOy6237H5wJmJ2Feh2Kcl/o9Au5vZ1GvksRcCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAhhUNQL+cxlA3ZAOuySKyNaYFkVKEjoo+0hPpMzxUl1TkHRIE +OllT5LzwjduFEvZRTE5aMiDpEUTM5XGw0zKd26Q9SC/vOnBwYl19oFhwyZ93bz2d +15eoyEYQOZf+VKmTEVw96epMrt98mACPO3hrGGtszW1zGEMRfcHZwIMHSM9INAGS +Rtjx5Ul0HR/srTZlc6kTpeLnnoBKO7TuPwqEav7MA7MWT62UGst0pz72o1B41sEY +rBIeJyGoFVg0NnET0nFBQcZqipdDjSWfUiDcMxOSve0s9IOpdLYtcMdark41gAXp +qY+Hy1uVXQx6UFJysbKaRZwgx4C/MnD/zs2a/LmGXdt1+7/AlrP7HG8NKkYCkf2o +mSs3ALgmt5wQ/DTwfVZM/6EPmnq1rU8s44DST0CDTlpVeYq0PkuweAQYYYgHBzc+ +hjx5b8k9xiqudhTveYCuV4AlIGTdIGbsK1F7HSnNE5S84dwcvcFL025TQyc3mEkK +HOdtGqBTZkxrehHSZs+Q17KwfrYx8Ad4OX/hXsszly4sgvV41jAcAN37hQCMbNkk +KlsaYXnsSDSkCt/jjh127hWaTzXJnCaQg9zijvlE8/ufYXoKW3+yCSBGrVt6cQ6d +/o1jnGh7D8mgLjTCR98trHaV2XZT8YiAPGbqf0RWmYdm2v/zubYl2Nor9KA= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.csr new file mode 100644 index 0000000..f960d97 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICtDCCAZwCAQAwbzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEoMCYGA1UEAwwfKi5r +dWJlY3RsZGlhbGluLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANU/MiB/kLKlcMhMtXuKIGu2riYUfmfUIz2MQNo+oz4wQHUG +g9P1sO3KT+kzfOFN7EkAzwryCIT3GEKTYoVY5X+Cllow3+IhtovgHhhrR9vSDBf6 +QSWqsVbai7U6jGQwtr9D+4Tv8O0vXtUWnTylE95/2Ecdf+n/7US435Rm9eg/CCsd +gPw/m/WTPA9mU7xrpou5Pwbb+TiCJsnQKbEGGuFilBdBLNZkj0qOAUMlYBu7A7n2 +yIljC5zJ93dlLX0kdssSiDwt0KcXwrWs7vhXxrnTRx6sEneloa10pbG20BPoqleG +Kk6LOy6237H5wJmJ2Feh2Kcl/o9Au5vZ1GvksRcCAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4IBAQAtZX8bZMGTkScmCE41UCNdAOXEb8NfnOXJW1TrnkLCgpl6fSen1g3N +/2GDyB4VZ6/F93DKBZY1zqQxr6V/qhwSyCOcM/1Kw0CvBP00QYhrVe0POFUOHpnB +nvGKjSy+5oRXN1F0VHJ3smy4WsPGAyRnFwsVxVKlArYAJe0Pu4zsNnopam/M8Bkl +CBwmmiTeBOqw+ue1P+uQ1R2J+V00oimhZoYqSYEfOhWkBw+lNp4z9iyFmYT028jD +kAsSeE5wBYWdF9lW63EqxvnZyY4bVq4K6FIOH2Ddf7mFkuXDp2V9/Xc47rwdbVSd +T3ImNZgN6u0fiSmNAQH8ydvGKOhX2mM8 +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.key new file mode 100644 index 0000000..9b36182 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1T8yIH+QsqVwyEy1e4oga7auJhR+Z9QjPYxA2j6jPjBAdQaD +0/Ww7cpP6TN84U3sSQDPCvIIhPcYQpNihVjlf4KWWjDf4iG2i+AeGGtH29IMF/pB +JaqxVtqLtTqMZDC2v0P7hO/w7S9e1RadPKUT3n/YRx1/6f/tRLjflGb16D8IKx2A +/D+b9ZM8D2ZTvGumi7k/Btv5OIImydApsQYa4WKUF0Es1mSPSo4BQyVgG7sDufbI +iWMLnMn3d2UtfSR2yxKIPC3QpxfCtazu+FfGudNHHqwSd6WhrXSlsbbQE+iqV4Yq +Tos7LrbfsfnAmYnYV6HYpyX+j0C7m9nUa+SxFwIDAQABAoIBAC63961zBSp+YGUh +mSkFq/CtRx4Eo2Tv7wCO965Yxr7bJ2P8F2majDasejFnHFboEXo0+Sc2u3MWB3Se +gZkhTrZOz83Wo1YReMLQSQekiUu5ivi91QQBj3St0KnVt+hDW+YvaK1JPAlatXYN +zEj7CY1XiPSbm+iSL1M9BY6nt/w3XJOUm+Zna+aNsUXhsyUBRb7tcXmG2HEQk1HF +oyXHngRk+OncjFYTeOfZV4srbz1+lp+dA2+eaTqbHlRbgRkXU4lT0uqTziTVH5nK +QFHvIoflYP0ScmmE0TmCvo2h47K8aCEHfnaaZVd40bh/32SzYzjlziSFB2GQEKNg +bfnCfqECgYEA76z1NmXmdOE5OXPqa5sFsK/rjQZmRaN3YM/62CwAzj5wKi2oYLLW +XbNwDbyHsutdqIVArUq7kU+QTWvuXsFm3QcEd8H7CS9y4aS8Y6XXLzxp8xWPyDSP +O7+RSCQKzAyCZJ/8SoFcVs+Jv1/QJ8Pfj6TYBe64vrdAUt5Ib2lrbp8CgYEA48Vr +cpRKxsNygusF7TebVFE1kIALQh11GnJ8+SplZ88TaUF4vGbiVTDOJ++v7UnLAeAG +v+jJ6OZFGCKcMEBsG3vR8CCAbs5qte/+KgoopaDb0LqiRbLAVqAdfUigi5hs0XoR +df9ypW9c44iWE0eDIZMj2FAsR4kh7PDBi+kuwokCgYAgvBe1uU8zkXtWXH2c+sxZ +KH3cgg4/3qa0Y1OYaNInWSHKsPe5VVOv8o0QYzqJdloRZTnDMVzS6PIFkQM6j+Gz +uHykuqrFw2RGa1CLr5/nRLIX0E8WAg+6El0OZFXRLrg0dCFqL6bj+G6Q484zUC5d +mR8kGEnFSpQPfWqZROW2cwKBgAgwLLCtzBJhLFiwgkII4biiQr6zG7SAwpj0zLFY +aNTWiOgbxm9fc3uEiaS95SEuqfGjUBrOD5Qj0xAPDnZdmoVSajWrtXJGUWfA/O4d +92Ac7EoQq4y95FQ45VZ/qeXa0O6m6MHJMSHpYvl7C4od5ATs+TjpiO35bBLMhOi2 +tUuhAoGBANHsvkzWxXGxwJ+H2dRbh4DLHqMYehgP+2Bt/goTD13MWPDt12ptM1QV +QmIhX4YHwybdJUilMiH4jpA6b9zd2lUPWmILKp1MeHczE3RJioQoJ8cRackWmbi1 +n856DbUV34dSStyw8cSQMb6dwIgAP6JyebaVcFQwCe/K2OPsMyPb +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.crt new file mode 100644 index 0000000..442e526 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQjCCAioCCQD0kF4yo85tRjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExNDIzNTk1 +M1oXDTMwMDExMTIzNTk1M1owaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEiMCAGA1UE +AwwZKi5rdWJld2ViLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALk/RVVcU7Di4xwE3Y2/pKyZbDQBvZ5YZy4LYT6WsWtGdKFT +tBNJUSJDjQlVq+g75X5mbf53EDP5VKJkrOd8v326NOlXTUVdEyUcHGUB7xrY4oFg +owG00fiuaDWRRVrOK01jrW2cmZ3tzqpHw998uO3gwLGiE/CL8xjcdWRl/zX3WqY3 +oVzmfSxTIdvMVAb+fiU/rlXUp9zNamYe5vG8P9XvNk8lf7pvgBNSb3pWlQoU7A41 +HLm0gqy+yauEvGq/nbbVyXQ7VAV7XxwoHEu2wabSC4iTfMHCx2sVd8P3WvBuBDf5 +4InwSW87NUJvtwzIRqlKcO1l1q2xWvWFeNUPc2cCAwEAATANBgkqhkiG9w0BAQsF +AAOCAgEArVNrGgaiHYQKIfs91Vor/A9GPRsj6o4VavCRiEjaBuPSq5G0v1fb8m9Z +B541y27N2QPdbYza2TdNqt22E4DcPXCi/JPSM73BLml3mw6ZuSLw+ghFFx3ZMN8X +RPknim1q9gmLbEU0zMy9kgjCY0OrUpmgfEE0DVQ5MxVovdz3B4QZ38hrKgwvYPyB +UBB5ovZYjPmaQPeRu0Cht9eSIm27c2/qAUrYNUWjmAVgrOQvKbIc+uBaj8at/7K2 +b6tOi6zyNdL+Mn6LpN+0tfz6H4NToROPXMaHXudoOMmYq2AHF9Ad8PjNsHZd30c7 +0muL2FEzD3t0P/9i80UNeKKTlY1BjOQ/npAxEoFoTpPSF58soLzO4GyXPIHZBkXa +DETmziyA18e+1vytyx2nMrC1qMc9tziZlWzOOL5THk1/plPaIoLD+rtPM2VwxBdH +W5mY7gSoqFDXQG/qaeLzF9uqFI32zmLHBcn6aMA6kiBkYK1HBcS48cC1LMvd/3lE +FTOdzhPzlRD2TZZDBsFXqDtP22oPBCwUMQDguvgajQGkUIlloipiba91LNIVLLGu +hmij1F43nXsygAXFNca9trXESPImataLvi15Gb+dad8ca0zfifncH/+ph4LtqsqI +trHkz7EhmlNVAKRSzExbcUPczzOGU3Ng7yoxezuVDnc1+2+6pec= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.csr new file mode 100644 index 0000000..dcd818a --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICrjCCAZYCAQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEiMCAGA1UEAwwZKi5r +dWJld2ViLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALk/RVVcU7Di4xwE3Y2/pKyZbDQBvZ5YZy4LYT6WsWtGdKFTtBNJUSJD +jQlVq+g75X5mbf53EDP5VKJkrOd8v326NOlXTUVdEyUcHGUB7xrY4oFgowG00fiu +aDWRRVrOK01jrW2cmZ3tzqpHw998uO3gwLGiE/CL8xjcdWRl/zX3WqY3oVzmfSxT +IdvMVAb+fiU/rlXUp9zNamYe5vG8P9XvNk8lf7pvgBNSb3pWlQoU7A41HLm0gqy+ +yauEvGq/nbbVyXQ7VAV7XxwoHEu2wabSC4iTfMHCx2sVd8P3WvBuBDf54InwSW87 +NUJvtwzIRqlKcO1l1q2xWvWFeNUPc2cCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IB +AQBH/vo7xzWwV3skhCW1rE/GCjPakEEBFypoNtQ3qDOGvY5c1W3QHQAXETVE2XYA +9rBEZe91RM05W1Bwxf1Abiddq7hGWcttqsQfKbvinRnya/tmNyv1nHNkpjkQIhrd +8uyGhpWwf5lrqXybStJTm1d4GUgH3ss/Pt8aZ2OjDWpRJ+ur1pGzGFpgOtGGoUV2 +2ZucrBn3sjQThux/tQdZ4R2IwSm+YExvBECWCh+Uvd9neK9CVoHQHD3uDug/0c9R +9sRQW2vbOidNyffnZ9wXcUAW6V0ccz1cI6lP0Ent7Qx691wn9kUr5ixvembfxUyH +jQuT79ngmEQq3aJ2Ol+pP0/9 +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.key new file mode 100644 index 0000000..6d2aec9 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubeweb/star.kubeweb.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEAuT9FVVxTsOLjHATdjb+krJlsNAG9nlhnLgthPpaxa0Z0oVO0 +E0lRIkONCVWr6DvlfmZt/ncQM/lUomSs53y/fbo06VdNRV0TJRwcZQHvGtjigWCj +AbTR+K5oNZFFWs4rTWOtbZyZne3OqkfD33y47eDAsaIT8IvzGNx1ZGX/Nfdapjeh +XOZ9LFMh28xUBv5+JT+uVdSn3M1qZh7m8bw/1e82TyV/um+AE1JvelaVChTsDjUc +ubSCrL7Jq4S8ar+dttXJdDtUBXtfHCgcS7bBptILiJN8wcLHaxV3w/da8G4EN/ng +ifBJbzs1Qm+3DMhGqUpw7WXWrbFa9YV41Q9zZwIDAQABAoIBAQCgvkpHe8oTX416 +rq4hZf5a1vaRWkFTuZou1ikXymNBlu11X9COO7xvYvZq9zAoYHEPcYpYmLAyqVPL +Zliytgjj7EQlhl/CpLUMKoxpCXiVb0V8xoIr5N+eTpiWguwPk373tFq1FoImDEic +cZGByBQgNjp+6QksXgQfuo0TBX6jL/m/6EDNoONtiPsHqZkRaADqoYiPE0kfyRaO +GmptOae0R8Y0Z4SkKnRqiokUzTIkjhf5QXYO0AkPXSkNgGS0Kf/JVnxyxfMMiCkF +sQHXoEeqMevnmuny6S0oXQkp+UbJBtCH+bZlQ4d9PWdRd8UZUDBdeyEu/NRun5qj +BS7zBRXBAoGBAOWNqkjYhq6lro5uu3pYkT2yFBQ9vQ0bXCRGuvG7fkqcH5+zgH4+ +WiqQuPVlNAG7Gg1D4XR1F5tIjPIs9qlY7G31aGOqYFdzWkEqQ6+wUXrM12rJAZut +Pfu/aheuEH0tMiwNvQt9GUl0CFY8HtKhG61HZRXxaotxOc48GqbP9OOvAoGBAM6W +3AgOGWVpEv1o+bn+g+38WgbQtvZOMLERSXMiPH7FBbkXAAZGoDLJ9uWaX602T1Gj +sjaOFqWrgkaC2h/erItpht5x9FBO80PgYXbZlLWZWJzsjkC9tnm7yyHSueZ8R8/Q +vBMD+NAdv9eqQJWw2Y3ZxKlSUHMkLQhdJvZtqwHJAoGBAOFywdQrfBMYu5g9nrkp +NPQf6mJ6xCqUPWmPYdk56G4EaXaih8b1G/8EBfNUwsAUbyCqE0oaGeYzWeIbUOG/ +aO0RgcWotlgM/Y5UoQdypoNxnPvOkFrBycdvFhZNqPoXolfr4kffRC+E2w8+mpMJ +EbKcFOZ5JMfwmvI2y4IbaYsXAoGBAIx25oRTkdDx8Id8qM9dzjYyLsQThVNTNqgu +KNKvpJJNQ+blTI731lSyHb1fiU4sbyIDO1j8o0lho+JtdgHVVVw1FA6LTKKOC7lL +BvnYVtRiv21zKgW6+CctGKCWtu+P8QPL2RTUzogysg3hJKii5kkcmCrIXQrsvfHB +5IwJZQyBAoGBAND4BL5oABu7ygLIUxJVxrvJlr5Uk4CUa6I5MPJu6Yb+RPbQW5kX +JEe5nl/OVyC/mc0yMZwPkudkYNHGNeaKwsjcQMNGHav6jnJoHHcdDmnXkO23xGYC +TTdKoXlCkC0NeFzpLAI0x5zwgd+1wKFfc4ZCCvwQAjx4AUKX+kYPN6hI +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.crt new file mode 100644 index 0000000..57d729c --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESDCCAjACCQD0kF4yo85tSTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDExNTAwMDUx +OFoXDTMwMDExMjAwMDUxOFowbzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEoMCYGA1UE +AwwfKi5rdWJld2ViZGlhbGluLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALX/q3xPMpTD9ewp87HtRs0XAOd4qcHIch75UMWb +eaW8nqd4xL5YTfe89yVDbdq4FHqqQ1rivcwp/WSHbNvRyvDMNlXZTS8PS6RqL75B +LljvzzrcIHax5WrUtrVUuFrlPN5yZW8fWB84ZY9EsX8UHB79rJdaS5r8B48/Ws/X +BIxiNQyIC9pNIfEDRQCjbn4GRCvCaf91tBxVwQGA38PF5JRSxblEx+Cte5d3ODH6 +5gCKMBVCsMn84gr/WGamZannth8/Lrasduq5Pvj/cJ2h4PUoh1oKwpSsXAG9d2tY +7AluVOVWvqJbvKKB1yegtZi77+D7oQ5ee3EOXriz+LWFsXMCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAm4oPL/mJAolyewL/jdbMWZT22oRKIYqxJQoHuTzeTQQInAUp +KbTUuRKgbSz0HeoRESrxV14BI3XrVJlbctkyBRpi14bQPd4TzdBxDY+Rh9bkuaHw +4zc8/mMYOT2xpq885BMTkd8YE98t3fyrrUYTBMCstBNMamcuV9hzA9RTuvtggUyT +3E6lgGLaHyFQUp7z++zDiNDrEhILC698zC2EYbqM50PqqB2kcfMRLmYKv4yRgQPZ +boSX15mc7ZBtU4Mb3bNPFR50PKbfrCi7KDIQiFse6TQONu7Pi3sN9GHVLfPhf7Nh +acJkweVgP2zZUObcW1BtrI+yvgGMh4rbXGzoIerBRQgC64eH/I0OVZBSy2EiJDna +T9tAczjvYAgR5qrXV33xY6DW6nPdUE0N5JgdHFoFn5uElEMiOyAWbAEvsQ6WtQCi +f689rh4CwU1XQ+pEQBtoj9vy3ioeTvDzSj8js/ofdzIMC5xLQdwyFUcGbx3ntwrn +4pQuny4A1rrK4pL8RQX7gvsXpuC+rlbew2IpbZ7CP6DJ8xbmktJtmfL0VsYiZjNr +zpL/YSAazr+NZYCpYguBrL9aIzU38QC7hIWvN5s22Mx0r6RsOP3jAFkZy9TFOJaO +HYfszg8SI1iG3jfB18TMLTcFDjVefM8rIsYiQ6ZPQQnP3uPht70Tz2vUHFI= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.csr new file mode 100644 index 0000000..276bd95 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICtDCCAZwCAQAwbzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxDDAKBgNVBAsMA0RldjEoMCYGA1UEAwwfKi5r +dWJld2ViZGlhbGluLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALX/q3xPMpTD9ewp87HtRs0XAOd4qcHIch75UMWbeaW8nqd4 +xL5YTfe89yVDbdq4FHqqQ1rivcwp/WSHbNvRyvDMNlXZTS8PS6RqL75BLljvzzrc +IHax5WrUtrVUuFrlPN5yZW8fWB84ZY9EsX8UHB79rJdaS5r8B48/Ws/XBIxiNQyI +C9pNIfEDRQCjbn4GRCvCaf91tBxVwQGA38PF5JRSxblEx+Cte5d3ODH65gCKMBVC +sMn84gr/WGamZannth8/Lrasduq5Pvj/cJ2h4PUoh1oKwpSsXAG9d2tY7AluVOVW +vqJbvKKB1yegtZi77+D7oQ5ee3EOXriz+LWFsXMCAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4IBAQBogrS+PnokRvp1rFYiYAEg4PPaIq6EYoPbRQS88ms0+MEjblXWW2EE +CnsXL/eFCQqi08HQCMzVAkAS9jqF0ZQ5X2tvVQB3OVddAOvlFBSFGhakoSDPuqZN +btQMfH//GRubwBftHbu66qKsltutHnobIts0LiIWxFsK0gNUOQDAU7ts44EDY5yq +iUR2slS077+jGFI4GvB2vmdNTX8JupOMH+60wpZIDSIwI919ANPTj3vBxdVmITP2 +V8wFtKl12Lznww2u8OcAH6g0v74FH+FfCZIVcJWBrinuWCmrw0VdG+s+D9/2PnkN +6XAarzL3eYilbin/aBE6NafdERkSCTat +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.key new file mode 100644 index 0000000..c4b3ffc --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-kubewebdialin/star.kubewebdialin.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAtf+rfE8ylMP17Cnzse1GzRcA53ipwchyHvlQxZt5pbyep3jE +vlhN97z3JUNt2rgUeqpDWuK9zCn9ZIds29HK8Mw2VdlNLw9LpGovvkEuWO/POtwg +drHlatS2tVS4WuU83nJlbx9YHzhlj0SxfxQcHv2sl1pLmvwHjz9az9cEjGI1DIgL +2k0h8QNFAKNufgZEK8Jp/3W0HFXBAYDfw8XklFLFuUTH4K17l3c4MfrmAIowFUKw +yfziCv9YZqZlqee2Hz8utqx26rk++P9wnaHg9SiHWgrClKxcAb13a1jsCW5U5Va+ +olu8ooHXJ6C1mLvv4PuhDl57cQ5euLP4tYWxcwIDAQABAoIBAGs88vaUU+GEdetp +7MXyYb2GyGmbl6svSM8naIiHS+50baeQaZ0euxI2rgmu6ZPu02JT4Mw0vlEGjOAn +S+VagJHSQlHjcK9B+/gs+WTJU13kL98mxZoqyFtiayX9fYNRf8Qy88G+XlZHDNCW +UV/noYpBPukPs2znBC1Lz16X+34bgBn1kydD5XrRi1WujYQV+Bd+qbyd5xfo3ICA +7aLo1mZF5ZRSYXz044w4E8pLzi3SfdvHBGqIHqhu1I7g/aumb//QbPUGinVi0lJp +Gne0fMSWC19Ueg3XUv2YgTOKRZ0teQ4gNTqJZU2omU+tNkQOL8Lem9EXgUr3ULmd +BEeWlgECgYEA72NahW69StWIQrxUB08ePKmYZhtpi7NlGsuMPVsJwIjn50LnmkPx +tb54OROqGYYPKeWj1LDaL8/WKSzU6Xi2JC5c4K7w4GsHP0Fibk7qMs4PI8gHNYUW +OR6SpbkHSj+XaO4Y5ZYxweEddjLmzBl1cU5xQFwE6B4v7QmjssoWcSECgYEAwqDQ +TwT/m0rY5KAZ7C1ziWhzTSnYDYkh8UyeW9jcbjGFafuWk5pbkGGJhiE8N899wfEB +xM44U5zryjWXfNnRVyL/0E0UUaIiEFXmbh9PpiMn67qIm0/bE4cTR5EK+1Cv5lH5 +40PcIAuaHCrwExF2n/o8G1Qw1CtM/2Mh5QwVzBMCgYEAvaj2p+6ztF13JoB1BpFt +gFxRrNwdBByuhssrodTF5i/Ro8zDF9qq3vY34+irhKtMykHxSeqQAv9kSp0FVrBX +hlThKvYPS44gYMJZzNFjBbtyDURGSeXWVIzfXMM6fl3hhLXXLdaQN242XFY5uwyU +mp+uZHJDRO/dwqw3cMhhfWECgYEAwcdhf7uHTzOnXYI2LRtX1HCllCdcUKW/oMNb +TDFPz3sIzWvMESv3jHpAdcZzcVHqcQWncC6ukDlF3m+bY5hvQKW9M8T6vQiDh+Yu +5mfQYqbQIXj4eEJ66idrv66e03gTmmr2NRukeFd02iS6IRIbHM8Ft2QMtcJjCEfI +QIfWz/cCgYEAw/zG/jKrkrk+TSgy6UuMgzFd9I2p8Fi8yl7QZFG2nB7uRtH80FFc +LyvsdfDDV+8NWUNgnKreBFWR5I9oI3vB74PMp27YrBV4L11ne1L5FXKGry5G2D1M +2GgkZSe34gTsmtM1tFF7mBNEs/DfMxURb0jsPw/9zC8ztqA+3QvV7jw= +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.crt new file mode 100644 index 0000000..20f0c48 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIETzCCAjcCCQD0kF4yo85tUjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDQwNzA3MjYy +N1oXDTMwMDQwNTA3MjYyN1owdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFcmFmYXkxFDASBgNVBAsMC2VuZ2luZWVyaW5n +MScwJQYDVQQDDB4qLmt1YmVjdGwucGVlci5yZWxheS5yYWZheS5kZXYwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAx45z7f2bXFjh0yPRaPqmiJWhRXDf +Q5JiyUlZlBfon12mmMx8C/1RY5NROd7xGN2Fokys7yNLq/PFl0NzoQDseJNO6aTg +wO1gi7MB3roiDFe1FRevsSapwu0ngOPovSShiFKsZOaXUeNoDT1d+eCZd9cu2Pwi +FhxCiblju6EckXKAPJGzD2SknLM8P5ky+n1+5801ebNoobP5E2/XSyaiyNSzqoIs +LkPgdYU9PpBH3CS0YDBSRgmjM1L3R0gO6Oy6KitgUEXXNQ4mCANcJX46bgfjt2iw +fjiiywJjLkRgBkhHUtzFPe19mma1P96a9XRKlrWtyCLWr0TRCxzftwjzAgMBAAEw +DQYJKoZIhvcNAQELBQADggIBAKc7sPjByTWLp/zPU1ALe0vRa1iytnPy1rdUdSeX +kEivS7ktsmPstD8c1jq9MmmkTQaOMTD4jbyd3YFPyhIH8NynI5OtADTdRTH7N2+M +bkDzW+zuGhxReErS7FsDJ30L3H8Ry2Ep2ZBCUD69KglEk7sJLKt5iQtyVAiuoH4Y +qn9xwGXdyAT1NiibepYSr5NNDeOanbeXTFZsGbDgYdE4QdWbIdlaj8aqmY2cw2LW +cflLjTZw0sIILGhJywjVjF29gb/1HykV8/UVYg344moKjjRaYUPFwFR2R/1vGfEB +O4aNM6e/AeBhY5WZIIxCFGQiG9o6QomzEFryD3sbx4V+qiDN3xd5AJEbpTvxOCts +3dUeacxmlaYfDPvJb3qR17sPgjot6Wxz8EcsHxqO/AvcBpcAZbLRsMszv78JICpT +FYnBJreOL6C9I7MjhQRS6DAhOExrCqgYaWxf8vDUXkY/Y0TFERwNsOhO5uIRPmBL +VroyQkamWvEfkobGRe9RL9pi5kxPp6VxColl7T/C53XTCku7il2avER/yTMG+ceB +E+htQLuPBkZt2vaDsvHaFgageFgmUYTps92Ku6cjBX1UYpAuaRIKRwCHLDd3PM27 +QwgrBIKx2LOuMlLpKF/D3emGqZHty4bWJTpXURMUofviZ6mi2kFaqTcySWCzCEuP +9maR +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.csr new file mode 100644 index 0000000..310d916 --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICuzCCAaMCAQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFcmFmYXkxFDASBgNVBAsMC2VuZ2luZWVyaW5nMScwJQYD +VQQDDB4qLmt1YmVjdGwucGVlci5yZWxheS5yYWZheS5kZXYwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDAx45z7f2bXFjh0yPRaPqmiJWhRXDfQ5JiyUlZ +lBfon12mmMx8C/1RY5NROd7xGN2Fokys7yNLq/PFl0NzoQDseJNO6aTgwO1gi7MB +3roiDFe1FRevsSapwu0ngOPovSShiFKsZOaXUeNoDT1d+eCZd9cu2PwiFhxCiblj +u6EckXKAPJGzD2SknLM8P5ky+n1+5801ebNoobP5E2/XSyaiyNSzqoIsLkPgdYU9 +PpBH3CS0YDBSRgmjM1L3R0gO6Oy6KitgUEXXNQ4mCANcJX46bgfjt2iwfjiiywJj +LkRgBkhHUtzFPe19mma1P96a9XRKlrWtyCLWr0TRCxzftwjzAgMBAAGgADANBgkq +hkiG9w0BAQsFAAOCAQEAimAaHYatwlCfq0OtI3tkASb+KkJxk49INZTswzQvyjvr +6mMFVplX+1fQGAYkZGJSjxwdAt2HnlgEyUybZHFFoBgnc/EQWlXfdY6kWIos5bgD +j3PyEEIQ0tCDnXQrABWZ7FSY7WRzEXmSQQwBznq5bprSKM0k5XBxXRo130xV7zCZ +fm2BTIl4hHc9hCc/UWQ6Z6R+pniiZQEaekZ6SpGjxdss5QaWMcvaDhtHSZi0wmRP +X+SyPsY6v3wv6Xp7cnMRsetthmm0iwy/Ctzk6Stl0KTFPqXoAttR/Oi5ynNT7CgY +KrTFCNr3UCY1xdr8vLYV+6RyIU9NdlgwnYrBiBU6gg== +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.key new file mode 100644 index 0000000..cd5399e --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-peer/star.kubectl.peer.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwMeOc+39m1xY4dMj0Wj6poiVoUVw30OSYslJWZQX6J9dppjM +fAv9UWOTUTne8RjdhaJMrO8jS6vzxZdDc6EA7HiTTumk4MDtYIuzAd66IgxXtRUX +r7EmqcLtJ4Dj6L0koYhSrGTml1HjaA09XfngmXfXLtj8IhYcQom5Y7uhHJFygDyR +sw9kpJyzPD+ZMvp9fufNNXmzaKGz+RNv10smosjUs6qCLC5D4HWFPT6QR9wktGAw +UkYJozNS90dIDujsuiorYFBF1zUOJggDXCV+Om4H47dosH44ossCYy5EYAZIR1Lc +xT3tfZpmtT/emvV0Spa1rcgi1q9E0Qsc37cI8wIDAQABAoIBAQC3M63r/Aqbv1n0 +WdvbIYG6C2jvVveaJTQTTyi2yWcjOXgdX25MoHQ/CXsxoOZ+htvWKFPnsGcBfcrv +Rv3h/+rJzxhm0OFQeefcbUZTi5+lxccAHqhg3eBZShZoCTQNtNn/7HZswILm4PpU +ouhjy5inH91N9wU9O3flv85/ugq1AtmL5jLp97qKyafWgy3nsQrPf5N+k59qCEXI ++giCKg3wKVgsFZW7bm+dBR6pgSvjfMnu3Yp5bJKNRQ4s/0DRcQJ9rsVvcQn5mWGW +NabBZgzb/nHNAEOFvzypinm96h4Ma4afLZhThvFKMpn5pc9+32rq5/nqbK3rg8sX +uNOOWLPpAoGBAOecyPI7nWcibSXsNFHKrAVdH8r7G+hY7v79+xdPx3FpQfSrlTFC +EBOmPBWTxk1Un80Wby8ObL2oqNcbg37Y78OzmEswyNJkcz24OHY3IVF0uCtPJQVw +JN2Z9cse/XOq/9RkSJzNtQOlSsezuDvid7tuWMmbGl6+OypNt+GOS1pfAoGBANUU +A1PVoN8V18rWJEsKpJrUavUaVFTC4D0XeVyV4+6YydP+mw2KdLEO0+xBA0YTUIsM +OLnY0FDPHby3XpjKoorBgGwm+CPbHVkUuqd5wuNn0b9ZdblFDFh0Ps+bzwEXs4d8 +isnPDOQaJuLlf20zTmIqnpZVGbZEb57oLaUhpgHtAoGAVcLlWWSfrQ+/ULp8yQh9 +1+KaoiOHOuqrXHoZHpGo7SKnjHx4Wuv2XgBA4VB9ria2oU88TjVOKJAqLLSAZVjY +tyNqMfVXdyhAz8rGBWf2i13n0AmXUMMss22N6O+Js+nOnwufI4i8DSSIYxf2ER6T +bTC36bbn9csEx25Nt1RkLIsCgYBUQAKEzm46Zw+ZAwz8ouus0Jqt6Ug474jsfvCc +nCOwvrbm3MAAHaPAMoW234nk1lQ9doqjaUgM+6fda3+62DxmstYNbWcl/khfZaPb +/ngBpJY5m6zBobyc6PHA8vFplC6HPsY9v2Fwe6qugmHdErFGfWWROXreJ7ce1Nzf +cyuJjQKBgQCRFhYBHj2yTaDrZjFpP7ybyGejVS2xGQ3+r54YWh1+MA41A0As97Kd +p1xK5KGxsdHOra6ApfsitGa8JjA7owFaMk+zrpCGyoRdoPAhyz/E9JksK8XW90s3 +zQffnMEuOoPp8GN51GlmsF2N5GpyMjUzuebrjTyYR1gX8qKTmERHaQ== +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.crt b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.crt new file mode 100644 index 0000000..3383f9d --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESzCCAjMCCQD0kF4yo85tUTANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDQwMzIzMTY0 +NFoXDTMwMDQwMTIzMTY0NFowcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFUmFmYXkxFDASBgNVBAsMC0VuZ2luZWVyaW5n +MSMwIQYDVQQDDBpzdGFyLnByb2JlLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKVRchQ2YyJTBqoeXiGVgDBm2wxZVIqqOcLP +TVVlRlgsIwboo1kfC1PNAUCcm5BSF1rqE9BZyKynYoOPlXRpI8LYNW+QMXyHg3/U +8chBEuJNkU3tGdbwN3EkuThbWs/7VbKnw/3tS0bjBX858l2yqVWcgKqxH1xe9q4j +f0RrC4+oZffwzSwkvjYTiVURW1Z/qT7VduwbkX6XzKc1JWLWOe5BJvlbZHZ13RWN +vm0lMGdwzAtoIS7lKF0p12kD+kp1EeW+Sz2fYC4jHQq/i8chol+2PrOTe7MUQvO1 +mlJdfbcK7HABU3oDAc3xl6LTRAVPzThHJKoJCj+drXDRrJzECaUCAwEAATANBgkq +hkiG9w0BAQsFAAOCAgEAmhwJ/5fEYevog5AXzFpINkZGxSTSsH9nMMPEjNNDNpfh +AliVNS+LVYISNe6w0vCJCNm2v6C/z/W3XlQ2leCXZd7aChWIVBoLjbg68sNbMyYA +QFSERXkX0sPTskmsz7i26nX/j0BgoJ8WFTwfpc+6JA9QjanTnT/uaCGdoSvdHotJ +1PzfzP/WnY1yq/YS73fRjf8rSaSv8qR1GbdRk2GXABXXwxjSLv3fQK7TzF+Pf7/w +dnzKCNw/4XWyF6PhnDOyRFHrKyqWp6YMctk52WYUpYg0o+zDSG7si7pNW+NJxkde +mnnLvWrgX5BMfSRnY3d0siQdyaGr2447z2Nr5gMNnsWv5NS43KrM+MXFFUUKMwuN +lGE9Y4mScVb9rxouYZ2HCqnL8vi3dK9oq5jMTjF/ytEF4ACprd6WRRlPLX4rR4sp +IXYyCGLRnCRBeMdKa5YDr08YsL3rOCWAc5EByALi/WQgFxr1qctqrylFhH88+eSB +7KfLVl4Ji4ti2ETqwzqtQ2kfcemNgWBwxodnqXpVF197aCiZ3gnjT5t93FSLXRXx +7JubuFzAeIOI/ICo2OiRZ2Gw+ANKSVtWnYODV/RiM/gRDYbv0jCHxA3PCbGJWTd1 +twx1oSo87N8gdC7NRL49bjtc6PRfPwow4/MUDLD0thjh29NaG7DiZf+1IsdGFvo= +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.csr b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.csr new file mode 100644 index 0000000..9c034bc --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICtzCCAZ8CAQAwcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFUmFmYXkxFDASBgNVBAsMC0VuZ2luZWVyaW5nMSMwIQYD +VQQDDBpzdGFyLnByb2JlLnJlbGF5LnJhZmF5LmRldjCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKVRchQ2YyJTBqoeXiGVgDBm2wxZVIqqOcLPTVVlRlgs +Iwboo1kfC1PNAUCcm5BSF1rqE9BZyKynYoOPlXRpI8LYNW+QMXyHg3/U8chBEuJN +kU3tGdbwN3EkuThbWs/7VbKnw/3tS0bjBX858l2yqVWcgKqxH1xe9q4jf0RrC4+o +ZffwzSwkvjYTiVURW1Z/qT7VduwbkX6XzKc1JWLWOe5BJvlbZHZ13RWNvm0lMGdw +zAtoIS7lKF0p12kD+kp1EeW+Sz2fYC4jHQq/i8chol+2PrOTe7MUQvO1mlJdfbcK +7HABU3oDAc3xl6LTRAVPzThHJKoJCj+drXDRrJzECaUCAwEAAaAAMA0GCSqGSIb3 +DQEBCwUAA4IBAQB9o+RvCbgUengcfOU8yLPtUwu3vVx0/4flcJvkq4UaYXIo6BqS +Jt97bzOYAc213YeAqzt2ldx50Ja/55CF/z5L0nwviKNk8MRyIlXy6TYPsWsfOG55 +LqOrZEUtAJA9CVrZDt097JWl+LWFKRnV3Y90GzsnS7SRNGrKKZxSbLArh+T8toz/ +zjfK1Q+Gz2TR6UOtc/0/kylon3SwGcJdyESAhQ/RrG10XGwSy30lT0Eyp92I9A/f +z+QJJrNugFkRwpBDzbiLIWjeVJqY3FSnuAqdKPSmsW4jVlzQhcLGIo/tN96BGjt9 +NZZVW0lpRX1K/5vL3fk992zIngep4o3355EL +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.key b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.key new file mode 100644 index 0000000..ed62d6b --- /dev/null +++ b/components/relay/testdata/certs/servercerts/star-probe/star.probe.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEApVFyFDZjIlMGqh5eIZWAMGbbDFlUiqo5ws9NVWVGWCwjBuij +WR8LU80BQJybkFIXWuoT0FnIrKdig4+VdGkjwtg1b5AxfIeDf9TxyEES4k2RTe0Z +1vA3cSS5OFtaz/tVsqfD/e1LRuMFfznyXbKpVZyAqrEfXF72riN/RGsLj6hl9/DN +LCS+NhOJVRFbVn+pPtV27BuRfpfMpzUlYtY57kEm+VtkdnXdFY2+bSUwZ3DMC2gh +LuUoXSnXaQP6SnUR5b5LPZ9gLiMdCr+LxyGiX7Y+s5N7sxRC87WaUl19twrscAFT +egMBzfGXotNEBU/NOEckqgkKP52tcNGsnMQJpQIDAQABAoIBAQCVaoxh6lolLsdK +QJIxZm7q0DvQ6Hg5r+ruwPCTaypuDM5Hs0HYDe/t+M7V9/W+NgP33rKz0EJIkiy+ +3apmAZUZyNJbGAcyF0Tp56vzo6UOtq7KsK53Drv9XgSuBBuK+Q2WoHzK54ru8ccJ +G3WL7CuSPhYosFe44/euolSnoNxdJJGrvO4jCs80BbkMrCqf0CHFLm+WdoCEDDY+ +2g0552tGPMOh7rTRP8myK+uphNZnKH6LJEW9DjfCqbAhAwonJXmQy3yGPd+cfxOw +UtdMnw6gWGQrrX5AQnQPDr7Y38UIKG5hoLohLacPdo3cs9CPA69TD9107392NuHh +OUGFzsNtAoGBANNE48HAKdLSlhhx+hC+TVoz4GTzrHIaBr+R+kEe+jq8BI9tUnX1 +mBdsVi1lss21TQBDxTcMd7kvDQQh/T83XUJxH0XvgDyVaeGIIAAcfhDo5FDT4b5b +VxvfW9ZWn7g5X3qTO289WzuUhZja4l8TV8c4RTUDBUZvNMFIvqCtrhbjAoGBAMhR +8Y2vN2dUNCZNVNRxLMzUroq3b7gZ2/G+AhjXY7RIGeEA62w19wMoX7AFB1ls5QET +Lyt+Tqii3OV8qjPBLm2eg2XiRDr44zTDEtscKUCsP1qBPYPWHgXOo5WTh1MJyvds +GsMVfJ50G0KtXNQlioxAtcty1h+ulcZg0yt4fbvXAoGBAJpVMdgAy8yxf2oeIOII +UrN5QAwfIbM8pf9d4SWfPoaXgPFRmDKJlXBO+Oky6JpzPevalf/vwKnr4CNLvRdo +F12JC6JoIhKfx3g0m8PZDJLHvrn4/miziv/We90o7IaEiuYdFgbeGH5+sdWG2GVB +2Ra0WYIvUbQxWqVzwaXZGlqnAoGBAI6cj+x/nmDouIvAKDFqNSvKCkzlVRrrBgOv +cwy+YtQZNJtULUaHkMhluOG2YsoOVWaTgtRjAprZ/4s9WmqoEGaoIAGruUmGoTKR +4l2tSc0qE5xaQDIYgJ0/xIQbWI8MfLvy8YaLSIICRNoOU/UQmPdKpmIl6jz57NUj +X5MmH9VXAoGAKNLUmkjdZL/1nM4jO27ayp7vNdQ7Jwlpta4LA+BODDnb5KM80EKb ++hIpeBgwytKJItm/PvVValW69iGgUx5Q+gN0sdKk+AtUQ3AvVnZf+C3pmkLqYvRl +ASCXe+3FkuQI6wu8s5E+K5BZa+eHtMr1MlVE3KZ+LQ6Z5XakNAD5seA= +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.crt b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.crt new file mode 100644 index 0000000..20f0c48 --- /dev/null +++ b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIETzCCAjcCCQD0kF4yo85tUjANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExEzARBgNVBAcMClBsZWFzYW50b24xDjAMBgNVBAoMBVJh +ZmF5MQwwCgYDVQQLDANEZXYxDjAMBgNVBAMMBVJhZmF5MB4XDTIwMDQwNzA3MjYy +N1oXDTMwMDQwNTA3MjYyN1owdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQsw +CQYDVQQHDAJQTDEOMAwGA1UECgwFcmFmYXkxFDASBgNVBAsMC2VuZ2luZWVyaW5n +MScwJQYDVQQDDB4qLmt1YmVjdGwucGVlci5yZWxheS5yYWZheS5kZXYwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAx45z7f2bXFjh0yPRaPqmiJWhRXDf +Q5JiyUlZlBfon12mmMx8C/1RY5NROd7xGN2Fokys7yNLq/PFl0NzoQDseJNO6aTg +wO1gi7MB3roiDFe1FRevsSapwu0ngOPovSShiFKsZOaXUeNoDT1d+eCZd9cu2Pwi +FhxCiblju6EckXKAPJGzD2SknLM8P5ky+n1+5801ebNoobP5E2/XSyaiyNSzqoIs +LkPgdYU9PpBH3CS0YDBSRgmjM1L3R0gO6Oy6KitgUEXXNQ4mCANcJX46bgfjt2iw +fjiiywJjLkRgBkhHUtzFPe19mma1P96a9XRKlrWtyCLWr0TRCxzftwjzAgMBAAEw +DQYJKoZIhvcNAQELBQADggIBAKc7sPjByTWLp/zPU1ALe0vRa1iytnPy1rdUdSeX +kEivS7ktsmPstD8c1jq9MmmkTQaOMTD4jbyd3YFPyhIH8NynI5OtADTdRTH7N2+M +bkDzW+zuGhxReErS7FsDJ30L3H8Ry2Ep2ZBCUD69KglEk7sJLKt5iQtyVAiuoH4Y +qn9xwGXdyAT1NiibepYSr5NNDeOanbeXTFZsGbDgYdE4QdWbIdlaj8aqmY2cw2LW +cflLjTZw0sIILGhJywjVjF29gb/1HykV8/UVYg344moKjjRaYUPFwFR2R/1vGfEB +O4aNM6e/AeBhY5WZIIxCFGQiG9o6QomzEFryD3sbx4V+qiDN3xd5AJEbpTvxOCts +3dUeacxmlaYfDPvJb3qR17sPgjot6Wxz8EcsHxqO/AvcBpcAZbLRsMszv78JICpT +FYnBJreOL6C9I7MjhQRS6DAhOExrCqgYaWxf8vDUXkY/Y0TFERwNsOhO5uIRPmBL +VroyQkamWvEfkobGRe9RL9pi5kxPp6VxColl7T/C53XTCku7il2avER/yTMG+ceB +E+htQLuPBkZt2vaDsvHaFgageFgmUYTps92Ku6cjBX1UYpAuaRIKRwCHLDd3PM27 +QwgrBIKx2LOuMlLpKF/D3emGqZHty4bWJTpXURMUofviZ6mi2kFaqTcySWCzCEuP +9maR +-----END CERTIFICATE----- diff --git a/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.csr b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.csr new file mode 100644 index 0000000..310d916 --- /dev/null +++ b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICuzCCAaMCAQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQH +DAJQTDEOMAwGA1UECgwFcmFmYXkxFDASBgNVBAsMC2VuZ2luZWVyaW5nMScwJQYD +VQQDDB4qLmt1YmVjdGwucGVlci5yZWxheS5yYWZheS5kZXYwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDAx45z7f2bXFjh0yPRaPqmiJWhRXDfQ5JiyUlZ +lBfon12mmMx8C/1RY5NROd7xGN2Fokys7yNLq/PFl0NzoQDseJNO6aTgwO1gi7MB +3roiDFe1FRevsSapwu0ngOPovSShiFKsZOaXUeNoDT1d+eCZd9cu2PwiFhxCiblj +u6EckXKAPJGzD2SknLM8P5ky+n1+5801ebNoobP5E2/XSyaiyNSzqoIsLkPgdYU9 +PpBH3CS0YDBSRgmjM1L3R0gO6Oy6KitgUEXXNQ4mCANcJX46bgfjt2iwfjiiywJj +LkRgBkhHUtzFPe19mma1P96a9XRKlrWtyCLWr0TRCxzftwjzAgMBAAGgADANBgkq +hkiG9w0BAQsFAAOCAQEAimAaHYatwlCfq0OtI3tkASb+KkJxk49INZTswzQvyjvr +6mMFVplX+1fQGAYkZGJSjxwdAt2HnlgEyUybZHFFoBgnc/EQWlXfdY6kWIos5bgD +j3PyEEIQ0tCDnXQrABWZ7FSY7WRzEXmSQQwBznq5bprSKM0k5XBxXRo130xV7zCZ +fm2BTIl4hHc9hCc/UWQ6Z6R+pniiZQEaekZ6SpGjxdss5QaWMcvaDhtHSZi0wmRP +X+SyPsY6v3wv6Xp7cnMRsetthmm0iwy/Ctzk6Stl0KTFPqXoAttR/Oi5ynNT7CgY +KrTFCNr3UCY1xdr8vLYV+6RyIU9NdlgwnYrBiBU6gg== +-----END CERTIFICATE REQUEST----- diff --git a/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.key b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.key new file mode 100644 index 0000000..cd5399e --- /dev/null +++ b/components/relay/testdata/certs/star.kubectl.peer.relay.rafay.dev.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwMeOc+39m1xY4dMj0Wj6poiVoUVw30OSYslJWZQX6J9dppjM +fAv9UWOTUTne8RjdhaJMrO8jS6vzxZdDc6EA7HiTTumk4MDtYIuzAd66IgxXtRUX +r7EmqcLtJ4Dj6L0koYhSrGTml1HjaA09XfngmXfXLtj8IhYcQom5Y7uhHJFygDyR +sw9kpJyzPD+ZMvp9fufNNXmzaKGz+RNv10smosjUs6qCLC5D4HWFPT6QR9wktGAw +UkYJozNS90dIDujsuiorYFBF1zUOJggDXCV+Om4H47dosH44ossCYy5EYAZIR1Lc +xT3tfZpmtT/emvV0Spa1rcgi1q9E0Qsc37cI8wIDAQABAoIBAQC3M63r/Aqbv1n0 +WdvbIYG6C2jvVveaJTQTTyi2yWcjOXgdX25MoHQ/CXsxoOZ+htvWKFPnsGcBfcrv +Rv3h/+rJzxhm0OFQeefcbUZTi5+lxccAHqhg3eBZShZoCTQNtNn/7HZswILm4PpU +ouhjy5inH91N9wU9O3flv85/ugq1AtmL5jLp97qKyafWgy3nsQrPf5N+k59qCEXI ++giCKg3wKVgsFZW7bm+dBR6pgSvjfMnu3Yp5bJKNRQ4s/0DRcQJ9rsVvcQn5mWGW +NabBZgzb/nHNAEOFvzypinm96h4Ma4afLZhThvFKMpn5pc9+32rq5/nqbK3rg8sX +uNOOWLPpAoGBAOecyPI7nWcibSXsNFHKrAVdH8r7G+hY7v79+xdPx3FpQfSrlTFC +EBOmPBWTxk1Un80Wby8ObL2oqNcbg37Y78OzmEswyNJkcz24OHY3IVF0uCtPJQVw +JN2Z9cse/XOq/9RkSJzNtQOlSsezuDvid7tuWMmbGl6+OypNt+GOS1pfAoGBANUU +A1PVoN8V18rWJEsKpJrUavUaVFTC4D0XeVyV4+6YydP+mw2KdLEO0+xBA0YTUIsM +OLnY0FDPHby3XpjKoorBgGwm+CPbHVkUuqd5wuNn0b9ZdblFDFh0Ps+bzwEXs4d8 +isnPDOQaJuLlf20zTmIqnpZVGbZEb57oLaUhpgHtAoGAVcLlWWSfrQ+/ULp8yQh9 +1+KaoiOHOuqrXHoZHpGo7SKnjHx4Wuv2XgBA4VB9ria2oU88TjVOKJAqLLSAZVjY +tyNqMfVXdyhAz8rGBWf2i13n0AmXUMMss22N6O+Js+nOnwufI4i8DSSIYxf2ER6T +bTC36bbn9csEx25Nt1RkLIsCgYBUQAKEzm46Zw+ZAwz8ouus0Jqt6Ug474jsfvCc +nCOwvrbm3MAAHaPAMoW234nk1lQ9doqjaUgM+6fda3+62DxmstYNbWcl/khfZaPb +/ngBpJY5m6zBobyc6PHA8vFplC6HPsY9v2Fwe6qugmHdErFGfWWROXreJ7ce1Nzf +cyuJjQKBgQCRFhYBHj2yTaDrZjFpP7ybyGejVS2xGQ3+r54YWh1+MA41A0As97Kd +p1xK5KGxsdHOra6ApfsitGa8JjA7owFaMk+zrpCGyoRdoPAhyz/E9JksK8XW90s3 +zQffnMEuOoPp8GN51GlmsF2N5GpyMjUzuebrjTyYR1gX8qKTmERHaQ== +-----END RSA PRIVATE KEY----- diff --git a/components/relay/testdata/client-cluster1.yaml b/components/relay/testdata/client-cluster1.yaml new file mode 100644 index 0000000..d904833 --- /dev/null +++ b/components/relay/testdata/client-cluster1.yaml @@ -0,0 +1,16 @@ + +dialouts: + kubectl: + proto: https + addr: cluster1.kubectldialin.relay.rafay.dev:443 + relay_service_sni: cluster1.kubectl.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + #use this if address is not avaialable from environment variable + upstream_addr: https://relay.rafay.dev:7443 + upstream_skip_verify: true + upstream_client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + upstream_client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + upstream_rootCA: testdata/certs/rootCA.crt + version: 1 \ No newline at end of file diff --git a/components/relay/testdata/client-cluster2.yaml b/components/relay/testdata/client-cluster2.yaml new file mode 100644 index 0000000..1224f08 --- /dev/null +++ b/components/relay/testdata/client-cluster2.yaml @@ -0,0 +1,16 @@ + +dialouts: + kubectl: + proto: https + addr: cluster2.kubectldialin.relay.rafay.dev:443 + relay_service_sni: cluster1.kubectl.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + #use this if address is not avaialable from environment variable + upstream_addr: https://relay.rafay.dev:7443 + upstream_skip_verify: true + upstream_client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + upstream_client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + upstream_rootCA: testdata/certs/rootCA.crt + version: 1 \ No newline at end of file diff --git a/components/relay/testdata/images/testsetup.png b/components/relay/testdata/images/testsetup.png new file mode 100644 index 0000000..69ef09f Binary files /dev/null and b/components/relay/testdata/images/testsetup.png differ diff --git a/components/relay/testdata/kubectlconfigs/kubeconfig b/components/relay/testdata/kubectlconfigs/kubeconfig new file mode 100644 index 0000000..a9fc18b --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfig @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWekNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFUzTnpneE1qZ3lOakFlRncweE9URXlNekV4TnpJd01qWmFGdzB5T1RFeU1qZ3hOekl3TWpaYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFUzTnpneE1qZ3lOakJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkNicWNTcklQQ2p5amE3cTg2Zk04SDlkMHJuMGlnYmpZaTRKalQ4c1BobTEKNXVVbm5NZHdVTytlRUY4eVU3cEdEUm04S2FSSER2QWF5SU0rbmloTkFsK2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFEMCtNT2piSXdxCm1ES3YveVVlSkFXVTVSemp0d3hmSHFmSjUwRnIrckxoMmdJZ0JWSjdMYkRwWXZKNEJDT3B5V0puMlJpRTBwY1IKZ0xZaUF1YlJNcXoxYU1jPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://192.168.56.103:6443 # set to your cluser IP + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +kind: Config +preferences: {} +users: +- name: default + user: + password: 15c80273fd3e8daae8a65ba86d8f6001 + username: admin diff --git a/components/relay/testdata/kubectlconfigs/kubeconfig-c-5 b/components/relay/testdata/kubectlconfigs/kubeconfig-c-5 new file mode 100644 index 0000000..f75e970 --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfig-c-5 @@ -0,0 +1,20 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lSQVBmN1cwSVg5NUlsMVA2MXpmcHpVV2d3Q2dZSUtvWkl6ajBFQXdJd2dZb3gKRERBS0JnTlZCQVlUQTFWVFFURVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCeE1KVTNWdQpibmwyWVd4bE1Sb3dHQVlEVlFRS0V4RlNZV1poZVNCVGVYTjBaVzF6SUVsdVl6RVZNQk1HQTFVRUN4TU1VbUZtCllYa2dVMlZ1ZEhKNU1SNHdIQVlEVlFRREV4VnlZV1poZVMxamIzSmxMWEpsYkdGNUxYVnpaWEl3SGhjTk1qQXcKTkRJNE1ESXdOakEyV2hjTk16QXdOREk0TURJd05qQTJXakNCaWpFTU1Bb0dBMVVFQmhNRFZWTkJNUk13RVFZRApWUVFJRXdwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSEV3bFRkVzV1ZVhaaGJHVXhHakFZQmdOVkJBb1RFVkpoClptRjVJRk41YzNSbGJYTWdTVzVqTVJVd0V3WURWUVFMRXd4U1lXWmhlU0JUWlc1MGNua3hIakFjQmdOVkJBTVQKRlhKaFptRjVMV052Y21VdGNtVnNZWGt0ZFhObGNqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQQpCR0MzcHp4NkRNalpvRVowSXJLT1p5cHduSWlEcVhZbkRDc0NUSFBZNHZJS3lWSWU3N3JBUDdhYXBvZVZvNjJPCjN0WW1WS0NuSjdUUUpIZjQyc3pkejhXalFqQkFNQTRHQTFVZER3RUIvd1FFQXdJQ2hEQWRCZ05WSFNVRUZqQVUKQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQmE3ZSthKzlSeXhMaDllZnBOT1ErK0hDcHI1SC95d3QwWUFCUFNqV3ZjN0FJZ01kZ0ZaV0R2ClBIMUtZYjB5VUJwRmNPcEVnRFdsdVRXb1NvUU9EclJETzNjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://4qkolkn.user.relay.rafay.local:10002 + #server: https://4qkolkn.user.relay.rafay.local:443 + name: c-5 +contexts: +- context: + cluster: c-5 + user: rafay-core-debug + name: c-5 +current-context: c-5 +kind: Config +preferences: {} +users: +- name: rafay-core-debug + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ6RENDQVhHZ0F3SUJBZ0lRYWhJQW9CZzJVdkV2ZjR6OEZCZkwvVEFLQmdncWhrak9QUVFEQWpDQmlqRU0KTUFvR0ExVUVCaE1EVlZOQk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhFd2xUZFc1dQplWFpoYkdVeEdqQVlCZ05WQkFvVEVWSmhabUY1SUZONWMzUmxiWE1nU1c1ak1SVXdFd1lEVlFRTEV3eFNZV1poCmVTQlRaVzUwY25reEhqQWNCZ05WQkFNVEZYSmhabUY1TFdOdmNtVXRjbVZzWVhrdGRYTmxjakFlRncweU1EQTAKTWpnd05qVXlOREphRncweU1EQTBNamt3TmpVeU5ESmFNQnN4R1RBWEJnTlZCQU1URUhKaFptRjVMV052Y21VdApaR1ZpZFdjd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSemhBWWsyYnB1MnAvTzRwbE5iMmYyCm5PTFoxM2M5cXlEYnBiOGN6RGpsdVpINVdUR0FMNFRQem5RYkFxaUNTNzNFTU4zR1lkRXRqZ0tGZFkzRGp4aWMKb3ljd0pUQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3SXdDZ1lJS29aSQp6ajBFQXdJRFNRQXdSZ0loQUplbWY2WHFpRDdQV3BuOWNqNVBMVTFJRUQ1VDg2ZFZPWDMyU1dyQXRqazlBaUVBCmkyQWl3Qkk4ZUxRejdWNGF6YVBLLzZwbkZ2c29hWlRINEhZY0JPMnhDOFk9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSVB5Q3IzcjJJdkRMREFiUjR1ajdPbVg3bnJtczlSZ0V3TXptRDQyaU1GY0tvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFYzRRR0pObTZidHFmenVLWlRXOW45cHppMmRkM1Bhc2cyNlcvSE13NDVibVIrVmt4Z0MrRQp6ODUwR3dLb2drdTl4RERkeG1IUkxZNENoWFdOdzQ4WW5BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= diff --git a/components/relay/testdata/kubectlconfigs/kubeconfig-sa b/components/relay/testdata/kubectlconfigs/kubeconfig-sa new file mode 100644 index 0000000..66adf6a --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfig-sa @@ -0,0 +1,19 @@ + +apiVersion: v1 +kind: Config +clusters: +- name: default-cluster + cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWekNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFUzTnpneE1qZ3lOakFlRncweE9URXlNekV4TnpJd01qWmFGdzB5T1RFeU1qZ3hOekl3TWpaYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFUzTnpneE1qZ3lOakJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkNicWNTcklQQ2p5amE3cTg2Zk04SDlkMHJuMGlnYmpZaTRKalQ4c1BobTEKNXVVbm5NZHdVTytlRUY4eVU3cEdEUm04S2FSSER2QWF5SU0rbmloTkFsK2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFEMCtNT2piSXdxCm1ES3YveVVlSkFXVTVSemp0d3hmSHFmSjUwRnIrckxoMmdJZ0JWSjdMYkRwWXZKNEJDT3B5V0puMlJpRTBwY1IKZ0xZaUF1YlJNcXoxYU1jPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://192.168.56.103:6443 +contexts: +- name: default-context + context: + cluster: default-cluster + namespace: default + user: default-user +current-context: default-context +users: +- name: default-user + user: + token: eyJhbGciOiJSUzI1NiIsImtpZCI6InBYSV8yNW81MXpaTnN4WGNwcGNYdHoxUUFxeklHenZBRmhyRTAyU211ZHMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6Im5hbWVzcGFjZS1hZG1pbi1zYS10b2tlbi14ajdubSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJuYW1lc3BhY2UtYWRtaW4tc2EiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjMjNlODc1Yi1kM2EzLTQ2YjEtOThlOS03YjkzODc0MGRkOTAiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpuYW1lc3BhY2UtYWRtaW4tc2EifQ.EdgMATO61V4katN3heaKdhPbyVd0vqc6FhG9ULmwZeXcQqgfZbnDIoOHbkCIPLCoL3CDfci-0JpiVmtG4_N3qxxZcyySsaHnWnyzaYEW0_FNjmtvKW13nwwM0t29iKu_AAaF-qk1dgJwvnQCR_2-nUb1NSjf_Hly25DkstHrsKfDlZM-fYR9HDlMdRXJ8xVX9YiMkAlOifgjqcQpzheoOxRd_yjGUmZVJUHBrjcT-1OdBmvhDjMU48dfdxadWQoymDaGMUbRsNKtPdrfMSrO_HBma5B_7HVJ2PbwQLice7ENltuue6cqR0GiYCKmMrBwDk6m1YweSZvaQIw5jv166A diff --git a/components/relay/testdata/kubectlconfigs/kubeconfigproxy-123abcd.user.relay.rafay.local b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-123abcd.user.relay.rafay.local new file mode 100644 index 0000000..ce628de --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-123abcd.user.relay.rafay.local @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Config +clusters: +- name: default-cluster + cluster: + server: https://123abcd.user.relay.rafay.local:443 + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lSQU5yYkV6ZDlsYm4yc0xXb3J1ZEMyZWN3Q2dZSUtvWkl6ajBFQXdJd2dZb3gKRERBS0JnTlZCQVlUQTFWVFFURVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCeE1KVTNWdQpibmwyWVd4bE1Sb3dHQVlEVlFRS0V4RlNZV1poZVNCVGVYTjBaVzF6SUVsdVl6RVZNQk1HQTFVRUN4TU1VbUZtCllYa2dVMlZ1ZEhKNU1SNHdIQVlEVlFRREV4VnlZV1poZVMxamIzSmxMWEpsYkdGNUxYVnpaWEl3SGhjTk1qQXcKTkRJeU1EYzFOakkxV2hjTk16QXdOREl5TURjMU5qSTFXakNCaWpFTU1Bb0dBMVVFQmhNRFZWTkJNUk13RVFZRApWUVFJRXdwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSEV3bFRkVzV1ZVhaaGJHVXhHakFZQmdOVkJBb1RFVkpoClptRjVJRk41YzNSbGJYTWdTVzVqTVJVd0V3WURWUVFMRXd4U1lXWmhlU0JUWlc1MGNua3hIakFjQmdOVkJBTVQKRlhKaFptRjVMV052Y21VdGNtVnNZWGt0ZFhObGNqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQQpCQnBYL2prbW5sNml2RzVrbndrcE1qa24vYmxuWk5tVjlLQTN6R00rTTZKOE5EVUNPT294QURSUmRIZFZOajltCjdTY0pKdlpJczczMk5kbjVreXFndEJlalFqQkFNQTRHQTFVZER3RUIvd1FFQXdJQ2hEQWRCZ05WSFNVRUZqQVUKQmdnckJnRUZCUWNEQWdZSUt3WUJCUVVIQXdFd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQkZhWHNtaWlicFpWVzNLZ3RvRm5OODdhQWZuaFZETXgvUmpSeDEzQjJNMEFJZ0FhTzZFUFMvClk4azZMSVhIMWVEMmxOdEsrVi9pMHhFQndEVVA3L0tyV2Y0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== +contexts: +- name: default-context + context: + cluster: default-cluster + namespace: default + user: default-user +current-context: default-context +users: +- name: default-user + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIyRENDQVg2Z0F3SUJBZ0lSQUxFR01TQUErajFLRCtuNllibUdrUW93Q2dZSUtvWkl6ajBFQXdJd2dZb3gKRERBS0JnTlZCQVlUQTFWVFFURVRNQkVHQTFVRUNCTUtRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCeE1KVTNWdQpibmwyWVd4bE1Sb3dHQVlEVlFRS0V4RlNZV1poZVNCVGVYTjBaVzF6SUVsdVl6RVZNQk1HQTFVRUN4TU1VbUZtCllYa2dVMlZ1ZEhKNU1SNHdIQVlEVlFRREV4VnlZV1poZVMxamIzSmxMWEpsYkdGNUxYVnpaWEl3SGhjTk1qQXcKTkRJME1ETXlPREExV2hjTk16QXdOREkwTURNeU9EQTFXakE4TVIwd0d3WURWUVFMRXhSaWNXWjJhR0ZpYVhCMApNMkV5WnpRMk9UZzNaekViTUJrR0ExVUVBeE1TYm1GdFpYTndZV05sTFdGa2JXbHVMWE5oTUZrd0V3WUhLb1pJCnpqMENBUVlJS29aSXpqMERBUWNEUWdBRU5lWGhyNTh3dzlRam5mSHRyd29yNlB3MlM3R0NrZ0plMlpMSm43bGUKSUxMVkVJcVVzUGVnY0Jvdzd3Q005ZTcrSjR5M1RpOEREa0U4R2NVcXU0UmVHS01TTUJBd0RnWURWUjBQQVFILwpCQVFEQWdXZ01Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRRGVCTmQvcXV2SVprN3pib3Jsb1dPTldkRVA3eDlCCjQ5eEhWMStISzU2ZHN3SWdmTXVVSFA2MVdJS1F5c1ZKdjFkbU1CeDUwdEUzRWJIU3piVlZJdURscTlnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUhRYVBBVGZUZW1uNzZ5bEg3TCs5d1VmTlFVYUljQWE4eERndmZyMnhKOTBvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFTmVYaHI1OHd3OVFqbmZIdHJ3b3I2UHcyUzdHQ2tnSmUyWkxKbjdsZUlMTFZFSXFVc1BlZwpjQm93N3dDTTllNytKNHkzVGk4RERrRThHY1VxdTRSZUdBPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= \ No newline at end of file diff --git a/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster1 b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster1 new file mode 100644 index 0000000..fe80584 --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster1 @@ -0,0 +1,24 @@ + +apiVersion: v1 +kind: Config +clusters: +- name: default-cluster + cluster: + server: https://123abcd.user.relay.rafay.local:443 + certificate-authority: /var/relayuserroot.crt +# server: https://cluster1.kubectl.relay.rafay.dev:443 +# certificate-authority: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/rootCA.crt +contexts: +- name: default-context + context: + cluster: default-cluster + namespace: default + user: default-user +current-context: default-context +users: +- name: default-user + user: + client-certificate: /var/relayuser.crt + client-key: /var/relayuser.key +# client-certificate: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/star.namespace-admin-sa.crt +# client-key: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/star.namespace-admin-sa.key diff --git a/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster2 b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster2 new file mode 100644 index 0000000..d3069fa --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeconfigproxy-cluster2 @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://cluster2.kubectl.relay.rafay.dev:443 + certificate-authority: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/rootCA.crt + name: default +contexts: +- context: + cluster: default + user: namespace-admin-sa + name: default +current-context: default +kind: Config +preferences: {} +users: +- name: namespace-admin-sa + user: + client-certificate: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/star.namespace-admin-sa.crt + client-key: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/star.namespace-admin-sa.key + diff --git a/components/relay/testdata/kubectlconfigs/kubeproxyconfig b/components/relay/testdata/kubectlconfigs/kubeproxyconfig new file mode 100644 index 0000000..d9a30bb --- /dev/null +++ b/components/relay/testdata/kubectlconfigs/kubeproxyconfig @@ -0,0 +1,23 @@ +apiVersion: v1 +clusters: +- cluster: + #certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJWekNCL3FBREFnRUNBZ0VBTUFvR0NDcUdTTTQ5QkFNQ01DTXhJVEFmQmdOVkJBTU1HR3N6Y3kxelpYSjIKWlhJdFkyRkFNVFUzTnpneE1qZ3lOakFlRncweE9URXlNekV4TnpJd01qWmFGdzB5T1RFeU1qZ3hOekl3TWpaYQpNQ014SVRBZkJnTlZCQU1NR0dzemN5MXpaWEoyWlhJdFkyRkFNVFUzTnpneE1qZ3lOakJaTUJNR0J5cUdTTTQ5CkFnRUdDQ3FHU000OUF3RUhBMElBQkNicWNTcklQQ2p5amE3cTg2Zk04SDlkMHJuMGlnYmpZaTRKalQ4c1BobTEKNXVVbm5NZHdVTytlRUY4eVU3cEdEUm04S2FSSER2QWF5SU0rbmloTkFsK2pJekFoTUE0R0ExVWREd0VCL3dRRQpBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSVFEMCtNT2piSXdxCm1ES3YveVVlSkFXVTVSemp0d3hmSHFmSjUwRnIrckxoMmdJZ0JWSjdMYkRwWXZKNEJDT3B5V0puMlJpRTBwY1IKZ0xZaUF1YlJNcXoxYU1jPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + certificate-authority: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/rootCA.crt + server: https://cluster1.kubectl.relay.rafay.dev:443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +kind: Config +preferences: {} +users: +- name: default + user: + client-certificate: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + client-key: /Users/stephanbenny/code/src/github.com/RafaySystems/rafay-relay/testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + #password: 15c80273fd3e8daae8a65ba86d8f6001 + #sername: admin + diff --git a/components/relay/testdata/pod.yaml b/components/relay/testdata/pod.yaml new file mode 100644 index 0000000..1947719 --- /dev/null +++ b/components/relay/testdata/pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: logs-inspect + namespace: rafay-system +spec: + containers: + - image: alpine:3.12 + name: logs + command: ["tail", "-f", "/dev/null"] + volumeMounts: + - mountPath: /opt/rafay/audit + name: audit-volume + volumes: + - name: audit-volume + persistentVolumeClaim: + claimName: rafay-relay-audit diff --git a/components/relay/testdata/relay-agent-config.yaml b/components/relay/testdata/relay-agent-config.yaml new file mode 100644 index 0000000..0a595bc --- /dev/null +++ b/components/relay/testdata/relay-agent-config.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +data: + clusterID: q72dg2g + relays: '[{"token":"bqjjlq6g10l2b7fm3v50","addr":"api.sentry.rafay.local:11000","endpoint":"*.core-connector.relay.rafay.local:10002","name":"rafay-core-relay-agent","templateToken":"bqjjln6g10l2lvudmup0"}]' +kind: ConfigMap +metadata: + name: relay-agent-config + namespace: rafay-system diff --git a/components/relay/testdata/role.yaml b/components/relay/testdata/role.yaml new file mode 100644 index 0000000..7192139 --- /dev/null +++ b/components/relay/testdata/role.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: default + namespace: rafay-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: relay-role +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +- nonResourceURLs: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: relay-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: relay-role +subjects: +- kind: ServiceAccount + name: default + namespace: rafay-system +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: rafay-core-debug + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: core-debug-role +rules: +- apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' +- nonResourceURLs: + - '*' + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: core-debug-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: relay-role +subjects: +- kind: ServiceAccount + name: rafay-core-debug + namespace: default diff --git a/components/relay/testdata/sample-kubeconfig.yaml b/components/relay/testdata/sample-kubeconfig.yaml new file mode 100644 index 0000000..d9a9770 --- /dev/null +++ b/components/relay/testdata/sample-kubeconfig.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWZ5Z0F3SUJBZ0lRSGdlWXlLaHlLWXNIY3E2TXNybG9HakFLQmdncWhrak9QUVFEQWpDQmlqRU0KTUFvR0ExVUVCaE1EVlZOQk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhFd2xUZFc1dQplWFpoYkdVeEdqQVlCZ05WQkFvVEVWSmhabUY1SUZONWMzUmxiWE1nU1c1ak1SVXdFd1lEVlFRTEV3eFNZV1poCmVTQlRaVzUwY25reEhqQWNCZ05WQkFNVEZYSmhabUY1TFdOdmNtVXRjbVZzWVhrdGRYTmxjakFlRncweU1EQTAKTWpjeU1EQTBORFJhRncwek1EQTBNamN5TURBME5EUmFNSUdLTVF3d0NnWURWUVFHRXdOVlUwRXhFekFSQmdOVgpCQWdUQ2tOaGJHbG1iM0p1YVdFeEVqQVFCZ05WQkFjVENWTjFibTU1ZG1Gc1pURWFNQmdHQTFVRUNoTVJVbUZtCllYa2dVM2x6ZEdWdGN5QkpibU14RlRBVEJnTlZCQXNUREZKaFptRjVJRk5sYm5SeWVURWVNQndHQTFVRUF4TVYKY21GbVlYa3RZMjl5WlMxeVpXeGhlUzExYzJWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRQpqUDh3V3lBNHNGdkh0ZlRXd1E5U3d0MmlzMWZPVi80dXVHY0VJcHhob2hOdVppeWcydElkOWtKNnY3OHpKUStGCkJQNEU0bkc0OFE0M0t2elhnS1JYVktOQ01FQXdEZ1lEVlIwUEFRSC9CQVFEQWdLRU1CMEdBMVVkSlFRV01CUUcKQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5QkFNQwpBMGdBTUVVQ0lRQy9tTmZiamhUVWN4L1BtbFdLSzYxMk9XejJNaTRqdytqVnN6SXZ2Z3lYWkFJZ2RtQ0wvTjd6CkMvSk1raDVkamx5RFhrMU53cUVUS04xeU52NVZpSjBGVlA0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + server: https://q72dg2g.user.relay.rafay.local:10002 + name: c-5 +contexts: +- context: + cluster: c-5 + user: rafay-core-debug + name: c-5 +current-context: c-5 +kind: Config +preferences: {} +users: +- name: rafay-core-debug + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ5ekNDQVhHZ0F3SUJBZ0lRZS8wbHdYYVZLR0pkcXBnSnFFQUNxakFLQmdncWhrak9QUVFEQWpDQmlqRU0KTUFvR0ExVUVCaE1EVlZOQk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhFd2xUZFc1dQplWFpoYkdVeEdqQVlCZ05WQkFvVEVWSmhabUY1SUZONWMzUmxiWE1nU1c1ak1SVXdFd1lEVlFRTEV3eFNZV1poCmVTQlRaVzUwY25reEhqQWNCZ05WQkFNVEZYSmhabUY1TFdOdmNtVXRjbVZzWVhrdGRYTmxjakFlRncweU1EQTAKTWpjeU16QTVNamxhRncweU1EQTBNamd5TXpBNU1qbGFNQnN4R1RBWEJnTlZCQU1URUhKaFptRjVMV052Y21VdApaR1ZpZFdjd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFRT2Z5bmZHR2NyYkgzRS9zSG12YmU2CjBtN1FuKzYyT0orRlhxWFhPREU2SzFuQzZSaVgzaUd1aDJjMlg0aGkyNk5ONk1aM1Uxemx5V3Rnb3dkazhzUDkKb3ljd0pUQU9CZ05WSFE4QkFmOEVCQU1DQmFBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3SXdDZ1lJS29aSQp6ajBFQXdJRFNBQXdSUUloQUk1MTRLOHNXNE8wWnRqbkM5Nkp3dGRENlFCRUx6d2szMWtCUW8zaEw3MnZBaUEyCkE5YUgxVWFnaGxadnNybXpzM0UzaG1UdHJxSG9xZFZyQnVhMXFQclRWQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUxsSW00Tk52VElVOFV3OTd3TnlKSmRZWjk0TmM2NGw1Y3lsRWRINjFONW9vQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFRG44cDN4aG5LMng5eFA3QjVyMjN1dEp1MEovdXRqaWZoVjZsMXpneE9pdFp3dWtZbDk0aApyb2RuTmwrSVl0dWpUZWpHZDFOYzVjbHJZS01IWlBMRC9RPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= diff --git a/components/relay/testdata/scripts/dialinclient.sh b/components/relay/testdata/scripts/dialinclient.sh new file mode 100644 index 0000000..1e6c7b1 --- /dev/null +++ b/components/relay/testdata/scripts/dialinclient.sh @@ -0,0 +1 @@ +curl -kv --cert ../certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt --key ../certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key https://cluster1.kubectldialin.relay.rafay.dev/ diff --git a/components/relay/testdata/scripts/loop.sh b/components/relay/testdata/scripts/loop.sh new file mode 100755 index 0000000..b2df53a --- /dev/null +++ b/components/relay/testdata/scripts/loop.sh @@ -0,0 +1,4 @@ +for ((n=0;n<1000;n++)) +do +KUBECONFIG=/tmp/kc-test.yaml kubectl get all -A +done \ No newline at end of file diff --git a/components/relay/testdata/scripts/relayclient.sh b/components/relay/testdata/scripts/relayclient.sh new file mode 100644 index 0000000..f5fc701 --- /dev/null +++ b/components/relay/testdata/scripts/relayclient.sh @@ -0,0 +1,16 @@ +#curl -kv --cert ../certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt --key ../certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key https://cluster1.kubectl.relay.rafay.dev/ + + +#curl -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key https://cluster1.kubectl.relay.rafay.dev/api?timeout=32s + + +#curl --http1.1 -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key https://cluster1.kubectl.relay.rafay.dev/apis/rbac.authorization.k8s.io/v1/?timeout=32s + + +#curl --http1.1 -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key https://cluster1.kubectl.relay.rafay.dev/ + +curl --http1.1 -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key POST --data '{"username":"xyz","password":"xyz"}' https://cluster1.kubectl.relay.rafay.dev/apis/rbac.authorization.k8s.io/v1/?timeout=32s + +#curl -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key POST --data '{"username":"xyz","password":"xyz"}' https://cluster1.kubectl.relay.rafay.dev/apis/rbac.authorization.k8s.io/v1?timeout=32s + +#curl --http1.1 -kv --cert ../certs/clientcerts/star.namespace-admin-sa.crt --key ../certs/clientcerts/star.namespace-admin-sa.key POST --data '{"username":"xyz","password":"xyz"}' https://cluster1.kubectl.relay.rafay.dev/api/v1/namespaces/default/pods/myapp1-pod/exec?command=%2Fbin%2Fsh&container=myapp1-container&stdin=true&stdout=true&tty=true \ diff --git a/components/relay/testdata/server.yaml b/components/relay/testdata/server.yaml new file mode 100644 index 0000000..52c418e --- /dev/null +++ b/components/relay/testdata/server.yaml @@ -0,0 +1,33 @@ + + +# sudo ifconfig lo0 alias 127.0.10.1 +relay_listen_addr: 127.0.10.1:443 + +relays: + kubectl: + proto: https + addr: :443 + server_name: kubectl.relay.rafay.dev + dialin_suffix: kubectldialin.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + server_crt: testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.crt + server_key: testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.key + version: 1 + + +dialins: + kubectldialin: + proto: https + addr: :443 + server_name: kubectldialin.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + server_crt: testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.crt + server_key: testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.key + version: 1 + +controller: + addr: + peer_probe_sni: star.probe.relay.rafay.dev:7001 + client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + root_ca: testdata/certs/rootCA.crt \ No newline at end of file diff --git a/components/relay/testdata/server2.yaml b/components/relay/testdata/server2.yaml new file mode 100644 index 0000000..1c2dcae --- /dev/null +++ b/components/relay/testdata/server2.yaml @@ -0,0 +1,33 @@ + + +# sudo ifconfig lo0 alias 127.0.20.1 +relay_listen_ip: 127.0.20.1:443 + +relays: + kubectl: + proto: https + addr: :443 + server_name: kubectl.relay.rafay.dev + dialin_suffix: kubectldialin.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + server_crt: testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.crt + server_key: testdata/certs/servercerts/star-kubectl/star.kubectl.relay.rafay.dev.key + version: 1 + + +dialins: + kubectldialin: + proto: https + addr: :443 + server_name: kubectldialin.relay.rafay.dev + root_ca: testdata/certs/rootCA.crt + server_crt: testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.crt + server_key: testdata/certs/servercerts/star-kubectldialin/star.kubectldialin.relay.rafay.dev.key + version: 1 + +controller: + addr: + peer_probe_sni: star.probe.relay.rafay.dev:7001 + client_crt: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.crt + client_key: testdata/certs/clientcerts/relayserver1-ABCD-123456.relay.rafay.dev.key + root_ca: testdata/certs/rootCA.crt \ No newline at end of file diff --git a/components/relay/testdata/testclients/loadtest.go b/components/relay/testdata/testclients/loadtest.go new file mode 100644 index 0000000..978cdae --- /dev/null +++ b/components/relay/testdata/testclients/loadtest.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "fmt" + "math/rand" + "os/exec" + "time" +) + +var min = 10 +var max = 50 + +func worker1(ctx context.Context, index int) { + var errCnt = 0 + var success = 0 + rand.Seed(time.Now().UnixNano()) + + for { + select { + case <-ctx.Done(): + time.Sleep(10 * time.Second) + fmt.Println("worker1 ", index, " done: success", success, "errcnt ", errCnt) + return + default: + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "../kubectlconfigs/kubeconfigproxy-123abcd.user.relay.rafay.local", "get", "pods", "-o", "yaml").Output() + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-5", "get", "all", "-A").Output() + out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-5", "get", "pods").Output() + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-5", "get", "pods", "-o", "yaml").Output() + if err != nil { + errCnt++ + } else { + success++ + } + intvl := time.Duration(rand.Intn(max-min+1)+min) * time.Millisecond + time.Sleep(intvl) + fmt.Printf("worker1[%d][%d][%d]: %s\n", index, success, errCnt, out) + } + } +} + +func worker2(ctx context.Context, index int) { + var errCnt = 0 + var success = 0 + rand.Seed(time.Now().UnixNano()) + + for { + select { + case <-ctx.Done(): + time.Sleep(10 * time.Second) + fmt.Println("worker2 ", index, " done: success", success, "errcnt ", errCnt) + return + default: + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "../kubectlconfigs/kubeconfigproxy-123abcd.user.relay.rafay.local", "get", "pods", "-o", "yaml").Output() + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-6", "get", "all", "-A").Output() + out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-6", "get", "pods").Output() + //out, err := exec.Command("/usr/local/bin/kubectl", "--kubeconfig", "/Users/stephanbenny/WorkSpace/kubeconfig-c-5", "get", "pods", "-o", "yaml").Output() + if err != nil { + errCnt++ + } else { + success++ + } + intvl := time.Duration(rand.Intn(max-min+1)+min) * time.Millisecond + time.Sleep(intvl) + fmt.Printf("worker2[%d][%d][%d]: %s\n", index, success, errCnt, out) + } + } +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + + for i := 1; i <= 10; i++ { + //go worker2(ctx, i) + go worker1(ctx, i) + + } + + time.Sleep(120 * time.Minute) + + cancel() + + time.Sleep(15 * time.Second) + +} diff --git a/components/relay/testdata/testclients/testclient.go b/components/relay/testdata/testclients/testclient.go new file mode 100644 index 0000000..1bb9bc1 --- /dev/null +++ b/components/relay/testdata/testclients/testclient.go @@ -0,0 +1,78 @@ +package main + +import ( + "bufio" + "fmt" + "net" + "os" + "strconv" + "sync" + "testing" + "time" +) + +const ( + //UNIXSOCKET .. + UNIXSOCKET = "/tmp/relay-unix-" +) + +func startClient(wg *sync.WaitGroup, buf []byte) { + defer wg.Done() + + fmt.Print("stat client") + buf3 := make([]byte, 4096) + // connect to this socket + socketPath := UNIXSOCKET + "kubectldialin.relay.rafay.dev" + conn, err := net.DialTimeout("unix", socketPath, 60*time.Second) + if err != nil { + fmt.Println("failed to connect ", err) + return + } + + fmt.Println("Connected to ", socketPath) + + for { + conn.Write(buf) + str := "GET /apis/rbac.authorization.k8s.io/v1?timeout=32s HTTP/1.1\r\nHost: 192.168.56.103:6443\r\nUser-Agent: curl/7.54.0\r\nAccept: */*\r\nX-Rafay-User:namespace-admin-sa\r\nX-Rafay-Namespace: default\r\n\r\n" + conn.Write([]byte(str)) + + conn.SetReadDeadline(time.Now().Add(10 * (time.Second))) + + for { + nr, err := conn.Read(buf3) + if err != nil { + fmt.Println("done reading ", err) + return + } + data := buf3[0:nr] + println("Client got:", string(data), nr) + } + } +} + +func TestRelayClient(t *testing.T) { + var wg sync.WaitGroup + + buf := make([]byte, 1024) + reader := bufio.NewReader(os.Stdin) + fmt.Print("Number of clients: ") + numStr, _ := reader.ReadString('\n') + fmt.Print("Key to send: ") + key, _ := reader.ReadString('\n') + + message := fmt.Sprintf("{\"DialinKey\": \"%s\", \"UserName\": \"namespace-admin-sa\", \"SNI\": \"cluster1.kubectldialin.relay.rafay.dev\"}", key[:len(key)-1]) + copy(buf, message) + + fmt.Print("numStr=", numStr) + num, err := strconv.Atoi(numStr[:len(numStr)-1]) + fmt.Println("num=", num, " err=", err) + for i := 0; i < num; i++ { + wg.Add(1) + go startClient(&wg, buf) + } + + fmt.Print("Waiting for workers") + wg.Wait() + + fmt.Print("Completed") +}