1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Feature] New gRPC and HTTP listeners (#1036)

This commit is contained in:
Nikita Vaniasin 2022-08-15 10:23:25 +02:00 committed by GitHub
parent b153b7183a
commit 583532e665
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1067 additions and 10 deletions

View file

@ -60,6 +60,7 @@
- (Bugfix) Infinite loop fix in ArangoD AsyncClient
- (Bugfix) Add Panic Handler
- (Bugfix) Unify yaml packages
- (Feature) Add new GRPC and HTTP API
## [1.2.13](https://github.com/arangodb/kube-arangodb/tree/1.2.13) (2022-06-07)
- (Bugfix) Fix arangosync members state inspection

View file

@ -89,6 +89,14 @@ else
COMPILE_DEBUG_FLAGS :=
endif
PROTOC_VERSION := 21.1
ifeq ($(shell uname),Darwin)
PROTOC_ARCHIVE_SUFFIX := osx-universal_binary
else
PROTOC_ARCHIVE_SUFFIX := linux-x86_64
endif
PROTOC_URL := https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${PROTOC_ARCHIVE_SUFFIX}.zip
ifeq ($(MANIFESTSUFFIX),-)
# Release setting
MANIFESTSUFFIX :=
@ -157,7 +165,7 @@ endif
EXCLUDE_DIRS := vendor .gobuild deps tools pkg/generated/clientset pkg/generated/informers pkg/generated/listers
EXCLUDE_FILES := *generated.deepcopy.go
SOURCES_QUERY := find ./ -type f -name '*.go' $(foreach EXCLUDE_DIR,$(EXCLUDE_DIRS), ! -path "*/$(EXCLUDE_DIR)/*") $(foreach EXCLUDE_FILE,$(EXCLUDE_FILES), ! -path "*/$(EXCLUDE_FILE)")
SOURCES_QUERY := find ./ -type f -name '*.go' ! -name '*.pb.go' $(foreach EXCLUDE_DIR,$(EXCLUDE_DIRS), ! -path "*/$(EXCLUDE_DIR)/*") $(foreach EXCLUDE_FILE,$(EXCLUDE_FILES), ! -path "*/$(EXCLUDE_FILE)")
SOURCES := $(shell $(SOURCES_QUERY))
DASHBOARDSOURCES := $(shell find $(DASHBOARDDIR)/src -name '*.js') $(DASHBOARDDIR)/package.json
LINT_EXCLUDES:=
@ -167,6 +175,8 @@ else
LINT_EXCLUDES+=.*\.enterprise\.go$$
endif
PROTOSOURCES := $(shell find ./ -type f -name '*.proto' $(foreach EXCLUDE_DIR,$(EXCLUDE_DIRS), ! -path "*/$(EXCLUDE_DIR)/*") | sort)
.DEFAULT_GOAL := all
.PHONY: all
all: check-vars verify-generated build
@ -185,7 +195,7 @@ allall: all
.PHONY: license-verify
license-verify:
@echo ">> Verify license of files"
@$(GOPATH)/bin/addlicense -f "./tools/codegen/license-header.txt" -check $(SOURCES)
@$(GOPATH)/bin/addlicense -f "./tools/codegen/license-header.txt" -check $(SOURCES) $(PROTOSOURCES)
.PHONY: fmt
fmt:
@ -196,7 +206,7 @@ fmt:
.PHONY: license
license:
@echo ">> Ensuring license of files"
@$(GOPATH)/bin/addlicense -f "./tools/codegen/license-header.txt" $(SOURCES)
@$(GOPATH)/bin/addlicense -f "./tools/codegen/license-header.txt" $(SOURCES) $(PROTOSOURCES)
.PHONY: fmt-verify
fmt-verify: license-verify
@ -464,6 +474,13 @@ tools: update-vendor
@GOBIN=$(GOPATH)/bin go install github.com/jessevdk/go-assets-builder@b8483521738fd2198ecfc378067a4e8a6079f8e5
@echo ">> Fetching gci"
@GOBIN=$(GOPATH)/bin go install github.com/daixiang0/gci@v0.3.0
@echo ">> Downloading protobuf compiler..."
@curl -L ${PROTOC_URL} -o $(GOPATH)/protoc.zip
@echo ">> Unzipping protobuf compiler..."
@unzip -o $(GOPATH)/protoc.zip -d $(GOPATH)/
@echo ">> Fetching protoc go plugins..."
@GOBIN=$(GOPATH)/bin go install github.com/golang/protobuf/protoc-gen-go@v1.5.2
@GOBIN=$(GOPATH)/bin go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
.PHONY: vendor
vendor:
@ -532,5 +549,13 @@ check-community:
_check:
@$(MAKE) fmt license-verify linter run-unit-tests bin
generate: generate-internal generate-proto fmt
generate-internal:
ROOT=$(ROOT) go test --count=1 "$(REPOPATH)/internal/..."
ROOT=$(ROOT) go test --count=1 "$(REPOPATH)/internal/..."
generate-proto:
PATH=$(PATH):$(GOBUILDDIR)/bin $(GOBUILDDIR)/bin/protoc -I.:$(GOBUILDDIR)/include/ \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
$(PROTOSOURCES)

View file

@ -19,6 +19,14 @@ spec:
port: 8528
protocol: TCP
targetPort: 8528
- name: http-api
port: 8628
protocol: TCP
targetPort: 8628
- name: grpc-api
port: 8728
protocol: TCP
targetPort: 8728
selector:
app.kubernetes.io/name: {{ template "kube-arangodb.name" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}

View file

@ -45,6 +45,7 @@ import (
"k8s.io/client-go/tools/record"
"k8s.io/klog"
"github.com/arangodb/kube-arangodb/pkg/api"
deploymentApi "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/crd"
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
@ -68,8 +69,12 @@ import (
const (
defaultServerHost = "0.0.0.0"
defaultServerPort = 8528
defaultAPIHTTPPort = 8628
defaultAPIGRPCPort = 8728
defaultLogLevel = "debug"
defaultAdminSecretName = "arangodb-operator-dashboard"
defaultAPIJWTSecretName = "arangodb-operator-api-jwt"
defaultAPIJWTKeySecretName = "arangodb-operator-api-jwt-key"
defaultAlpineImage = "alpine:3.7"
defaultMetricsExporterImage = "arangodb/arangodb-exporter:0.1.6"
defaultArangoImage = "arangodb/arangodb:latest"
@ -98,6 +103,14 @@ var (
adminSecretName string // Name of basic authentication secret containing the admin username+password of the dashboard
allowAnonymous bool // If set, anonymous access to dashboard is allowed
}
apiOptions struct {
enabled bool
httpPort int
grpcPort int
jwtSecretName string
jwtKeySecretName string
tlsSecretName string
}
operatorOptions struct {
enableDeployment bool // Run deployment operator
enableDeploymentReplication bool // Run deployment-replication operator
@ -158,6 +171,12 @@ func init() {
f.StringVar(&serverOptions.adminSecretName, "server.admin-secret-name", defaultAdminSecretName, "Name of secret containing username + password for login to the dashboard")
f.BoolVar(&serverOptions.allowAnonymous, "server.allow-anonymous-access", false, "Allow anonymous access to the dashboard")
f.StringArrayVar(&logLevels, "log.level", []string{defaultLogLevel}, fmt.Sprintf("Set log levels in format <level> or <logger>=<level>. Possible loggers: %s", strings.Join(logging.Global().Names(), ", ")))
f.BoolVar(&apiOptions.enabled, "api.enabled", true, "Enable operator HTTP and gRPC API")
f.IntVar(&apiOptions.httpPort, "api.http-port", defaultAPIHTTPPort, "HTTP API port to listen on")
f.IntVar(&apiOptions.grpcPort, "api.grpc-port", defaultAPIGRPCPort, "gRPC API port to listen on")
f.StringVar(&apiOptions.tlsSecretName, "api.tls-secret-name", "", "Name of secret containing tls.crt & tls.key for HTTPS API (if empty, self-signed certificate is used)")
f.StringVar(&apiOptions.jwtSecretName, "api.jwt-secret-name", defaultAPIJWTSecretName, "Name of secret which will contain JWT to authenticate API requests.")
f.StringVar(&apiOptions.jwtKeySecretName, "api.jwt-key-secret-name", defaultAPIJWTKeySecretName, "Name of secret containing key used to sign JWT. If there is no such secret present, value will be saved here")
f.BoolVar(&operatorOptions.enableDeployment, "operator.deployment", false, "Enable to run the ArangoDeployment operator")
f.BoolVar(&operatorOptions.enableDeploymentReplication, "operator.deployment-replication", false, "Enable to run the ArangoDeploymentReplication operator")
f.BoolVar(&operatorOptions.enableStorage, "operator.storage", false, "Enable to run the ArangoLocalStorage operator")
@ -306,6 +325,37 @@ func executeMain(cmd *cobra.Command, args []string) {
logger.Err(err).Fatal("Failed to create operator")
}
if apiOptions.enabled {
apiServerCfg := api.ServerConfig{
Namespace: namespace,
ServerName: name,
ServerAltNames: []string{ip},
HTTPAddress: net.JoinHostPort("0.0.0.0", strconv.Itoa(apiOptions.httpPort)),
GRPCAddress: net.JoinHostPort("0.0.0.0", strconv.Itoa(apiOptions.grpcPort)),
TLSSecretName: apiOptions.tlsSecretName,
JWTSecretName: apiOptions.jwtSecretName,
JWTKeySecretName: apiOptions.jwtKeySecretName,
LivelinessProbe: &livenessProbe,
ProbeDeployment: api.ReadinessProbeConfig{
Enabled: cfg.EnableDeployment,
Probe: &deploymentProbe,
},
ProbeDeploymentReplication: api.ReadinessProbeConfig{
Enabled: cfg.EnableDeploymentReplication,
Probe: &deploymentReplicationProbe,
},
ProbeStorage: api.ReadinessProbeConfig{
Enabled: cfg.EnableStorage,
Probe: &storageProbe,
},
}
apiServer, err := api.NewServer(client.Kubernetes().CoreV1(), apiServerCfg)
if err != nil {
logger.Err(err).Fatal("Failed to create API server")
}
go utilsError.LogError(logger, "while running API server", apiServer.Run)
}
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
if svr, err := server.NewServer(client.Kubernetes().CoreV1(), server.Config{
Namespace: namespace,
@ -348,7 +398,7 @@ func executeMain(cmd *cobra.Command, args []string) {
}); err != nil {
logger.Err(err).Fatal("Failed to create HTTP server")
} else {
go utilsError.LogError(logger, "error while starting service", svr.Run)
go utilsError.LogError(logger, "error while starting server", svr.Run)
}
// startChaos(context.Background(), cfg.KubeCli, cfg.Namespace, chaosLevel)

View file

@ -13,3 +13,4 @@
- [Additional configuration](./additional_configuration.md)
- [Topology awareness](./topology_awareness.md)
- [Configuring timezone](./configuring_tz.md)
- [Operator API](./api.md)

30
docs/design/api.md Normal file
View file

@ -0,0 +1,30 @@
# Operator API
A running operator exposes HTTP and gRPC API listeners to allow retrieving and setting some configuration values programmatically.
Both listeners require a secured connection to be established. It is possible to provide TLS certificate via k8s secret
using command line option `--api.tls-secret-name`. If secret name is not provided, operator will use self-signed certificate.
Some HTTP endpoints require the authorization to work with. All gRPC endpoints require the authorization.
The authorization can be accomplished by providing JWT token in 'Authorization' header, e.g. `Authorization: Bearer <token>`
The JWT token can be fetched from k8s secret (by default `arangodb-operator-api-jwt`). The token is generated automatically
on operator startup using the signing key specified in `arangodb-operator-api-jwt-key` secret. If it is empty or not exists,
the signing key will be auto-generated and saved into secret. You can specify other signing key using `--api.jwt-key-secret-name` CLI option.
## HTTP
The HTTP API is running at endpoint specified by operator command line options `--api.http-port` (8628 by default).
The HTTP API exposes endpoints used to get operator health and readiness status, operator version, and prometheus-compatible metrics.
For now only `/metrics` endpoint require authorization.
## gRPC
The gRPC API is running at endpoint specified by operator command line options `--api.grpc-port` (8728 by default).
The gRPC API is exposed to allow programmatic access to some operator features and status.
gRPC protobuf definitions and go-client can be found at `github.com/kube-arangodb/pkg/api/server` package.
All gRPC requests require per-RPC metadata set to contain a valid Authorization header.

11
go.mod
View file

@ -45,7 +45,9 @@ require (
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
google.golang.org/grpc v1.47.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
k8s.io/api v0.21.10
k8s.io/apiextensions-apiserver v0.18.3
@ -54,6 +56,11 @@ require (
k8s.io/klog v1.0.0
)
require (
github.com/arangodb/rebalancer v0.1.1 // indirect
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect
)
require (
github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect
github.com/beorn7/perks v1.0.1 // indirect
@ -68,7 +75,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/googleapis/gnostic v0.4.1 // indirect
@ -95,7 +102,7 @@ require (
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.0
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.9.0 // indirect

18
go.sum
View file

@ -97,6 +97,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -132,6 +136,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@ -226,8 +231,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -488,6 +494,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -612,6 +619,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -822,6 +831,8 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U=
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -842,6 +853,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -854,8 +867,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

130
pkg/api/api.go Normal file
View file

@ -0,0 +1,130 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"net"
"net/http"
"time"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
pb "github.com/arangodb/kube-arangodb/pkg/api/server"
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/util/probe"
)
var apiLogger = logging.Global().RegisterAndGetLogger("api-server", logging.Info)
type Server struct {
httpServer *http.Server
grpcServer *grpc.Server
grpcAddress string
pb.UnimplementedOperatorServer
}
type ReadinessProbeConfig struct {
Enabled bool
Probe *probe.ReadyProbe
}
// ServerConfig settings for the Server
type ServerConfig struct {
Namespace string
ServerName string
ServerAltNames []string
HTTPAddress string
GRPCAddress string
TLSSecretName string
JWTSecretName string
JWTKeySecretName string
LivelinessProbe *probe.LivenessProbe
ProbeDeployment ReadinessProbeConfig
ProbeDeploymentReplication ReadinessProbeConfig
ProbeStorage ReadinessProbeConfig
}
// NewServer creates and configure a new Server
func NewServer(cli typedCore.CoreV1Interface, cfg ServerConfig) (*Server, error) {
jwtSigningKey, err := ensureJWT(cli, cfg)
if err != nil {
return nil, err
}
tlsConfig, err := prepareTLSConfig(cli, cfg)
if err != nil {
return nil, err
}
auth := &authorization{jwtSigningKey: jwtSigningKey}
s := &Server{
httpServer: &http.Server{
Addr: cfg.HTTPAddress,
ReadTimeout: time.Second * 30,
ReadHeaderTimeout: time.Second * 15,
WriteTimeout: time.Second * 30,
TLSConfig: tlsConfig,
},
grpcServer: grpc.NewServer(
grpc.UnaryInterceptor(auth.ensureGRPCAuth),
grpc.Creds(credentials.NewTLS(tlsConfig)),
),
grpcAddress: cfg.GRPCAddress,
}
handler, err := buildHTTPHandler(cfg, auth)
if err != nil {
return nil, err
}
s.httpServer.Handler = handler
pb.RegisterOperatorServer(s.grpcServer, s)
return s, nil
}
func (s *Server) Run() error {
g := errgroup.Group{}
g.Go(func() error {
apiLogger.Info("Serving HTTP API on %s", s.httpServer.Addr)
if err := s.httpServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
return err
}
return nil
})
g.Go(func() error {
apiLogger.Info("Serving GRPC API on %s", s.grpcAddress)
ln, err := net.Listen("tcp", s.grpcAddress)
if err != nil {
return err
}
defer ln.Close()
if err := s.grpcServer.Serve(ln); err != nil && err != grpc.ErrServerStopped {
return err
}
return nil
})
return g.Wait()
}

82
pkg/api/auth.go Normal file
View file

@ -0,0 +1,82 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"context"
"net/http"
"strings"
"github.com/gin-gonic/gin"
jg "github.com/golang-jwt/jwt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type authorization struct {
jwtSigningKey string
}
func (a *authorization) isValid(token string) bool {
t, err := jg.Parse(token, func(_ *jg.Token) (interface{}, error) {
return []byte(a.jwtSigningKey), nil
})
if err != nil {
apiLogger.Err(err).Info("invalid JWT: %s", token)
return false
}
return t.Valid
}
// ensureHTTPAuth ensure a valid token exists within HTTP request header
func (a *authorization) ensureHTTPAuth(c *gin.Context) {
h := c.Request.Header.Values("Authorization")
bearerToken := extractBearerToken(h)
if !a.isValid(bearerToken) {
c.AbortWithStatus(http.StatusUnauthorized)
}
}
// ensureGRPCAuth ensures a valid token exists within a GRPC request's metadata
func (a *authorization) ensureGRPCAuth(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.InvalidArgument, "missing metadata")
}
// The keys within metadata.MD are normalized to lowercase.
// See: https://godoc.org/google.golang.org/grpc/metadata#New
bearerToken := extractBearerToken(md["authorization"])
if !a.isValid(bearerToken) {
return nil, status.Errorf(codes.Unauthenticated, "invalid token")
}
// Continue execution of handler after ensuring a valid token.
return handler(ctx, req)
}
func extractBearerToken(authorization []string) string {
if len(authorization) < 1 {
return ""
}
return strings.TrimPrefix(authorization[0], "Bearer ")
}

39
pkg/api/grpc.go Normal file
View file

@ -0,0 +1,39 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"context"
pb "github.com/arangodb/kube-arangodb/pkg/api/server"
"github.com/arangodb/kube-arangodb/pkg/version"
)
func (s *Server) GetVersion(ctx context.Context, _ *pb.Empty) (*pb.Version, error) {
v := version.GetVersionV1()
return &pb.Version{
Version: string(v.Version),
Build: v.Build,
Edition: string(v.Edition),
GoVersion: v.GoVersion,
BuildDate: v.BuildDate,
}, nil
}

79
pkg/api/http.go Normal file
View file

@ -0,0 +1,79 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"net/http"
"github.com/gin-gonic/gin"
prometheus "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
"github.com/arangodb/kube-arangodb/pkg/util/probe"
"github.com/arangodb/kube-arangodb/pkg/version"
)
func buildHTTPHandler(cfg ServerConfig, auth *authorization) (http.Handler, error) {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
versionV1Responder, err := operatorHTTP.NewSimpleJSONResponse(version.GetVersionV1())
if err != nil {
return nil, errors.WithStack(err)
}
r.GET("/_api/version", gin.WrapF(versionV1Responder.ServeHTTP))
r.GET("/api/v1/version", gin.WrapF(versionV1Responder.ServeHTTP))
r.GET("/health", gin.WrapF(cfg.LivelinessProbe.LivenessHandler))
var readyProbes []*probe.ReadyProbe
if cfg.ProbeDeployment.Enabled {
r.GET("/ready/deployment", gin.WrapF(cfg.ProbeDeployment.Probe.ReadyHandler))
readyProbes = append(readyProbes, cfg.ProbeDeployment.Probe)
}
if cfg.ProbeDeploymentReplication.Enabled {
r.GET("/ready/deployment-replication", gin.WrapF(cfg.ProbeDeploymentReplication.Probe.ReadyHandler))
readyProbes = append(readyProbes, cfg.ProbeDeploymentReplication.Probe)
}
if cfg.ProbeStorage.Enabled {
r.GET("/ready/storage", gin.WrapF(cfg.ProbeStorage.Probe.ReadyHandler))
readyProbes = append(readyProbes, cfg.ProbeStorage.Probe)
}
r.GET("/ready", gin.WrapF(handleGetReady(readyProbes...)))
r.GET("/metrics", auth.ensureHTTPAuth, gin.WrapH(prometheus.Handler()))
return r, nil
}
func handleGetReady(probes ...*probe.ReadyProbe) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
for _, probe := range probes {
if !probe.IsReady() {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
w.WriteHeader(http.StatusOK)
}
}

95
pkg/api/jwt.go Normal file
View file

@ -0,0 +1,95 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"context"
"crypto/rand"
"fmt"
"time"
jg "github.com/golang-jwt/jwt"
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
secret "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret/v1"
)
// ensureJWT ensure that JWT signing key exists or creates a new one.
// It also saves new token into secret if it is not present.
// Returns JWT signing key.
func ensureJWT(cli typedCore.CoreV1Interface, cfg ServerConfig) (string, error) {
secrets := cli.Secrets(cfg.Namespace)
signingKey, err := k8sutil.GetTokenSecret(context.Background(), secrets, cfg.JWTKeySecretName)
if err != nil && k8sutil.IsNotFound(err) || signingKey == "" {
signingKey, err = createSigningKey(secrets, cfg.JWTKeySecretName)
if err != nil {
return "", err
}
} else if err != nil {
return "", errors.WithStack(err)
}
_, err = k8sutil.GetTokenSecret(context.Background(), secrets, cfg.JWTSecretName)
if err != nil && k8sutil.IsNotFound(err) {
err = generateAndSaveJWT(secrets, cfg)
if err != nil {
return "", err
}
} else if err != nil {
return "", errors.WithStack(err)
}
return signingKey, nil
}
// generateAndSaveJWT tries to generate new JWT using signing key retrieved from secret.
// If it is not present, it creates a new key.
// The resulting JWT is stored in secrets.
func generateAndSaveJWT(secrets secret.Interface, cfg ServerConfig) error {
claims := jg.MapClaims{
"iss": fmt.Sprintf("kube-arangodb/%s", cfg.ServerName),
"iat": time.Now().Unix(),
}
err := k8sutil.CreateJWTFromSecret(context.Background(), secrets, secrets, cfg.JWTSecretName, cfg.JWTKeySecretName, claims, nil)
if err != nil {
return errors.WithStack(err)
}
return err
}
func createSigningKey(secrets secret.ModInterface, keySecretName string) (string, error) {
signingKey := make([]byte, 32)
_, err := rand.Read(signingKey)
if err != nil {
return "", errors.WithStack(err)
}
err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(context.Background(), func(ctxChild context.Context) error {
return k8sutil.CreateTokenSecret(ctxChild, secrets, keySecretName, string(signingKey), nil)
})
if err != nil {
return "", errors.WithStack(err)
}
return string(signingKey), nil
}

View file

@ -0,0 +1,261 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.21.1
// source: pkg/api/server/operator.proto
package server
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Empty struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Empty) Reset() {
*x = Empty{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_api_server_operator_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Empty) ProtoMessage() {}
func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_pkg_api_server_operator_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
func (*Empty) Descriptor() ([]byte, []int) {
return file_pkg_api_server_operator_proto_rawDescGZIP(), []int{0}
}
type Version struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
Build string `protobuf:"bytes,2,opt,name=build,proto3" json:"build,omitempty"`
Edition string `protobuf:"bytes,3,opt,name=edition,proto3" json:"edition,omitempty"`
GoVersion string `protobuf:"bytes,4,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"`
BuildDate string `protobuf:"bytes,5,opt,name=build_date,json=buildDate,proto3" json:"build_date,omitempty"`
}
func (x *Version) Reset() {
*x = Version{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_api_server_operator_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Version) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Version) ProtoMessage() {}
func (x *Version) ProtoReflect() protoreflect.Message {
mi := &file_pkg_api_server_operator_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Version.ProtoReflect.Descriptor instead.
func (*Version) Descriptor() ([]byte, []int) {
return file_pkg_api_server_operator_proto_rawDescGZIP(), []int{1}
}
func (x *Version) GetVersion() string {
if x != nil {
return x.Version
}
return ""
}
func (x *Version) GetBuild() string {
if x != nil {
return x.Build
}
return ""
}
func (x *Version) GetEdition() string {
if x != nil {
return x.Edition
}
return ""
}
func (x *Version) GetGoVersion() string {
if x != nil {
return x.GoVersion
}
return ""
}
func (x *Version) GetBuildDate() string {
if x != nil {
return x.BuildDate
}
return ""
}
var File_pkg_api_server_operator_proto protoreflect.FileDescriptor
var file_pkg_api_server_operator_proto_rawDesc = []byte{
0x0a, 0x1d, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x22, 0x91, 0x01, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07,
0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65,
0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64,
0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64,
0x44, 0x61, 0x74, 0x65, 0x32, 0x3a, 0x0a, 0x08, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
0x12, 0x2e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d,
0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x00,
0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61,
0x72, 0x61, 0x6e, 0x67, 0x6f, 0x64, 0x62, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x2d, 0x61, 0x72, 0x61,
0x6e, 0x67, 0x6f, 0x64, 0x62, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_pkg_api_server_operator_proto_rawDescOnce sync.Once
file_pkg_api_server_operator_proto_rawDescData = file_pkg_api_server_operator_proto_rawDesc
)
func file_pkg_api_server_operator_proto_rawDescGZIP() []byte {
file_pkg_api_server_operator_proto_rawDescOnce.Do(func() {
file_pkg_api_server_operator_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_api_server_operator_proto_rawDescData)
})
return file_pkg_api_server_operator_proto_rawDescData
}
var file_pkg_api_server_operator_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_pkg_api_server_operator_proto_goTypes = []interface{}{
(*Empty)(nil), // 0: server.Empty
(*Version)(nil), // 1: server.Version
}
var file_pkg_api_server_operator_proto_depIdxs = []int32{
0, // 0: server.Operator.GetVersion:input_type -> server.Empty
1, // 1: server.Operator.GetVersion:output_type -> server.Version
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_pkg_api_server_operator_proto_init() }
func file_pkg_api_server_operator_proto_init() {
if File_pkg_api_server_operator_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pkg_api_server_operator_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Empty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_api_server_operator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Version); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_api_server_operator_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_pkg_api_server_operator_proto_goTypes,
DependencyIndexes: file_pkg_api_server_operator_proto_depIdxs,
MessageInfos: file_pkg_api_server_operator_proto_msgTypes,
}.Build()
File_pkg_api_server_operator_proto = out.File
file_pkg_api_server_operator_proto_rawDesc = nil
file_pkg_api_server_operator_proto_goTypes = nil
file_pkg_api_server_operator_proto_depIdxs = nil
}

View file

@ -0,0 +1,39 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
syntax = "proto3";
option go_package = "github.com/arangodb/kube-arangodb/pkg/api/server";
package server;
service Operator {
rpc GetVersion (Empty) returns (Version) {}
}
message Empty {}
message Version {
string version = 1;
string build = 2;
string edition = 3;
string go_version = 4;
string build_date = 5;
}

View file

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.1
// source: pkg/api/server/operator.proto
package server
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// OperatorClient is the client API for Operator service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type OperatorClient interface {
GetVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Version, error)
}
type operatorClient struct {
cc grpc.ClientConnInterface
}
func NewOperatorClient(cc grpc.ClientConnInterface) OperatorClient {
return &operatorClient{cc}
}
func (c *operatorClient) GetVersion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Version, error) {
out := new(Version)
err := c.cc.Invoke(ctx, "/server.Operator/GetVersion", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// OperatorServer is the server API for Operator service.
// All implementations must embed UnimplementedOperatorServer
// for forward compatibility
type OperatorServer interface {
GetVersion(context.Context, *Empty) (*Version, error)
mustEmbedUnimplementedOperatorServer()
}
// UnimplementedOperatorServer must be embedded to have forward compatible implementations.
type UnimplementedOperatorServer struct {
}
func (UnimplementedOperatorServer) GetVersion(context.Context, *Empty) (*Version, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedOperatorServer) mustEmbedUnimplementedOperatorServer() {}
// UnsafeOperatorServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to OperatorServer will
// result in compilation errors.
type UnsafeOperatorServer interface {
mustEmbedUnimplementedOperatorServer()
}
func RegisterOperatorServer(s grpc.ServiceRegistrar, srv OperatorServer) {
s.RegisterService(&Operator_ServiceDesc, srv)
}
func _Operator_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OperatorServer).GetVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/server.Operator/GetVersion",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OperatorServer).GetVersion(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
// Operator_ServiceDesc is the grpc.ServiceDesc for Operator service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Operator_ServiceDesc = grpc.ServiceDesc{
ServiceName: "server.Operator",
HandlerType: (*OperatorServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetVersion",
Handler: _Operator_GetVersion_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "pkg/api/server/operator.proto",
}

91
pkg/api/tls.go Normal file
View file

@ -0,0 +1,91 @@
//
// DISCLAIMER
//
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package api
import (
"context"
"crypto/tls"
"time"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
typedCore "k8s.io/client-go/kubernetes/typed/core/v1"
"github.com/arangodb-helper/go-certificates"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
func prepareTLSConfig(cli typedCore.CoreV1Interface, cfg ServerConfig) (*tls.Config, error) {
cert, key, err := loadOrSelfSignCertificate(cli, cfg)
if err != nil {
return nil, errors.WithStack(err)
}
tlsConfig, err := createTLSConfig(cert, key)
if err != nil {
return nil, errors.WithStack(err)
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// loadOrSelfSignCertificate loads TLS certificate from secret or creates a new one
func loadOrSelfSignCertificate(cli typedCore.CoreV1Interface, cfg ServerConfig) (string, string, error) {
if cfg.TLSSecretName != "" {
// Load TLS certificate from secret
s, err := cli.Secrets(cfg.Namespace).Get(context.Background(), cfg.TLSSecretName, meta.GetOptions{})
if err != nil {
return "", "", err
}
certBytes, found := s.Data[core.TLSCertKey]
if !found {
return "", "", errors.Newf("No %s found in secret %s", core.TLSCertKey, cfg.TLSSecretName)
}
keyBytes, found := s.Data[core.TLSPrivateKeyKey]
if !found {
return "", "", errors.Newf("No %s found in secret %s", core.TLSPrivateKeyKey, cfg.TLSSecretName)
}
return string(certBytes), string(keyBytes), nil
}
// Secret not specified, create our own TLS certificate
options := certificates.CreateCertificateOptions{
CommonName: cfg.ServerName,
Hosts: append([]string{cfg.ServerName}, cfg.ServerAltNames...),
ValidFrom: time.Now(),
ValidFor: time.Hour * 24 * 365 * 10,
IsCA: false,
ECDSACurve: "P256",
}
return certificates.CreateCertificate(options, nil)
}
// createTLSConfig creates a TLS config based on given config
func createTLSConfig(cert, key string) (*tls.Config, error) {
var result *tls.Config
c, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
return nil, errors.WithStack(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
}

View file

@ -34,7 +34,7 @@ type Timezone struct {
}
func (t Timezone) GetData() ([]byte, bool) {
if d, ok := timezonesData[t.Parent]; ok {
if d, ok := timezonesData[t.Name]; ok {
if d, err := base64.StdEncoding.DecodeString(d); err == nil {
return d, true
}