initial commit for relay (#17)

initial commit for relay
This commit is contained in:
nirav-rafay
2022-02-22 11:42:15 +05:30
committed by GitHub
parent f4bd2a802d
commit ac594927a6
106 changed files with 12547 additions and 0 deletions
+50
View File
@@ -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"]
+65
View File
@@ -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"]
+8
View File
@@ -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"]
+33
View File
@@ -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
+137
View File
@@ -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 <file-name>
```
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 /<PATH>/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
```
+114
View File
@@ -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/
File diff suppressed because it is too large Load Diff
+148
View File
@@ -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)
}
}
}
+595
View File
@@ -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 := &register.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
}
}
}
+36
View File
@@ -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
}
}
}
+97
View File
@@ -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)
}
+30
View File
@@ -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
}
+150
View File
@@ -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)
}
}
+31
View File
@@ -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
}
+299
View File
@@ -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
}
+12
View File
@@ -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)
+214
View File
@@ -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")
}
}
@@ -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
}
+244
View File
@@ -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
}
+34
View File
@@ -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
+888
View File
@@ -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 := &register.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 := &register.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 := &register.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 := &register.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 := &register.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 := &register.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")
}
@@ -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
}
@@ -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
}
@@ -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
}
}
}
+109
View File
@@ -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 := &register.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),
)
}
+213
View File
@@ -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)
}
+241
View File
@@ -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
}
+217
View File
@@ -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
}
+80
View File
@@ -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
}
+76
View File
@@ -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
}
}
+397
View File
@@ -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
}
+15
View File
@@ -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()
}
+651
View File
@@ -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
}
}
}
@@ -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
}
@@ -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")
}
+189
View File
@@ -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)
}
}
+381
View File
@@ -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, " }")
}
+11
View File
@@ -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")
)
+168
View File
@@ -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
}
}
}
File diff suppressed because it is too large Load Diff
@@ -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
}
+713
View File
@@ -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
}
+89
View File
@@ -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
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
+15
View File
@@ -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
+30
View File
@@ -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-----
+51
View File
@@ -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-----
+1
View File
@@ -0,0 +1 @@
F4905E32A3CE6D52
+53
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
CB6AAE2A400B8C7E
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
@@ -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-----
+16
View File
@@ -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
+16
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

+19
View File
@@ -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
+20
View File
@@ -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=
+19
View File
@@ -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
@@ -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=
@@ -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
@@ -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
@@ -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
+17
View File
@@ -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
+8
View File
@@ -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
+70
View File
@@ -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
+19
View File
@@ -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=
+1
View File
@@ -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/

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