Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace HTTP server with HTTPS server #112

Merged
merged 1 commit into from
Apr 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/client"
"github.com/arangodb/kube-arangodb/pkg/logging"
"github.com/arangodb/kube-arangodb/pkg/operator"
"github.com/arangodb/kube-arangodb/pkg/server"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
"github.com/arangodb/kube-arangodb/pkg/util/probe"
Expand All @@ -69,12 +70,13 @@ var (
Run: cmdMainRun,
}

logLevel string
cliLog = logging.NewRootLogger()
logService logging.Service
server struct {
host string
port int
logLevel string
cliLog = logging.NewRootLogger()
logService logging.Service
serverOptions struct {
host string
port int
tlsSecretName string
}
operatorOptions struct {
enableDeployment bool // Run deployment operator
Expand All @@ -89,8 +91,9 @@ var (

func init() {
f := cmdMain.Flags()
f.StringVar(&server.host, "server.host", defaultServerHost, "Host to listen on")
f.IntVar(&server.port, "server.port", defaultServerPort, "Port to listen on")
f.StringVar(&serverOptions.host, "server.host", defaultServerHost, "Host to listen on")
f.IntVar(&serverOptions.port, "server.port", defaultServerPort, "Port to listen on")
f.StringVar(&serverOptions.tlsSecretName, "server.tls-secret-name", "", "Name of secret containing tls.crt & tls.key for HTTPS server (if empty, self-signed certificate is used)")
f.StringVar(&logLevel, "log.level", defaultLogLevel, "Set initial log level")
f.BoolVar(&operatorOptions.enableDeployment, "operator.deployment", false, "Enable to run the ArangoDeployment operator")
f.BoolVar(&operatorOptions.enableStorage, "operator.storage", false, "Enable to run the ArangoLocalStorage operator")
Expand Down Expand Up @@ -133,19 +136,40 @@ func cmdMainRun(cmd *cobra.Command, args []string) {
if len(name) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodName)
}
ip := os.Getenv(constants.EnvOperatorPodIP)
if len(ip) == 0 {
cliLog.Fatal().Msgf("%s environment variable missing", constants.EnvOperatorPodIP)
}

// Get host name
id, err := os.Hostname()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to get hostname")
}

http.HandleFunc("/health", probe.LivenessHandler)
http.HandleFunc("/ready/deployment", deploymentProbe.ReadyHandler)
http.HandleFunc("/ready/storage", storageProbe.ReadyHandler)
http.Handle("/metrics", prometheus.Handler())
listenAddr := net.JoinHostPort(server.host, strconv.Itoa(server.port))
go http.ListenAndServe(listenAddr, nil)
// Create kubernetes client
kubecli, err := k8sutil.NewKubeClient()
if err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create Kubernetes client")
}

mux := http.NewServeMux()
mux.HandleFunc("/health", probe.LivenessHandler)
mux.HandleFunc("/ready/deployment", deploymentProbe.ReadyHandler)
mux.HandleFunc("/ready/storage", storageProbe.ReadyHandler)
mux.Handle("/metrics", prometheus.Handler())
listenAddr := net.JoinHostPort(serverOptions.host, strconv.Itoa(serverOptions.port))
if svr, err := server.NewServer(kubecli.CoreV1(), mux, server.Config{
Address: listenAddr,
TLSSecretName: serverOptions.tlsSecretName,
TLSSecretNamespace: namespace,
PodName: name,
PodIP: ip,
}); err != nil {
cliLog.Fatal().Err(err).Msg("Failed to create HTTP server")
} else {
go svr.Run()
}

cfg, deps, err := newOperatorConfigAndDeps(id+"-"+name, namespace, name)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions manifests/templates/deployment/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: metrics
containerPort: 8528
livenessProbe:
httpGet:
path: /health
port: 8528
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready/deployment
port: 8528
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
6 changes: 6 additions & 0 deletions manifests/templates/storage/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,25 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
ports:
- name: metrics
containerPort: 8528
livenessProbe:
httpGet:
path: /health
port: 8528
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready/storage
port: 8528
scheme: HTTPS
initialDelaySeconds: 5
periodSeconds: 10

29 changes: 29 additions & 0 deletions pkg/server/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// DISCLAIMER
//
// Copyright 2018 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
//
// Author Ewout Prangsma
//

package server

import "github.com/pkg/errors"

var (
maskAny = errors.WithStack
)
126 changes: 126 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// DISCLAIMER
//
// Copyright 2018 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
//
// Author Ewout Prangsma
//

package server

import (
"crypto/tls"
"fmt"
"net/http"
"time"

certificates "github.com/arangodb-helper/go-certificates"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

// Config settings for the Server
type Config struct {
Address string // Address to listen on
TLSSecretName string // Name of secret containing TLS certificate
TLSSecretNamespace string // Namespace of secret containing TLS certificate
PodName string // Name of the Pod we're running in
PodIP string // IP address of the Pod we're running in
}

// Server is the HTTPS server for the operator.
type Server struct {
httpServer *http.Server
}

// NewServer creates a new server, fetching/preparing a TLS certificate.
func NewServer(cli corev1.CoreV1Interface, handler http.Handler, cfg Config) (*Server, error) {
httpServer := &http.Server{
Addr: cfg.Address,
Handler: handler,
ReadTimeout: time.Second * 30,
ReadHeaderTimeout: time.Second * 15,
WriteTimeout: time.Second * 30,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
}

var cert, key string
if cfg.TLSSecretName != "" && cfg.TLSSecretNamespace != "" {
// Load TLS certificate from secret
s, err := cli.Secrets(cfg.TLSSecretNamespace).Get(cfg.TLSSecretName, metav1.GetOptions{})
if err != nil {
return nil, maskAny(err)
}
certBytes, found := s.Data[v1.TLSCertKey]
if !found {
return nil, maskAny(fmt.Errorf("No %s found in secret %s", v1.TLSCertKey, cfg.TLSSecretName))
}
keyBytes, found := s.Data[v1.TLSPrivateKeyKey]
if !found {
return nil, maskAny(fmt.Errorf("No %s found in secret %s", v1.TLSPrivateKeyKey, cfg.TLSSecretName))
}
cert = string(certBytes)
key = string(keyBytes)
} else {
// Secret not specified, create our own TLS certificate
options := certificates.CreateCertificateOptions{
CommonName: cfg.PodName,
Hosts: []string{cfg.PodName, cfg.PodIP},
ValidFrom: time.Now(),
ValidFor: time.Hour * 24 * 365 * 10,
IsCA: false,
ECDSACurve: "P256",
}
var err error
cert, key, err = certificates.CreateCertificate(options, nil)
if err != nil {
return nil, maskAny(err)
}
}
tlsConfig, err := createTLSConfig(cert, key)
if err != nil {
return nil, maskAny(err)
}
tlsConfig.BuildNameToCertificate()
httpServer.TLSConfig = tlsConfig

return &Server{
httpServer: httpServer,
}, nil
}

// Run the server until the program stops.
func (s *Server) Run() error {
if err := s.httpServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
return maskAny(err)
}
return 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, maskAny(err)
}
result = &tls.Config{
Certificates: []tls.Certificate{c},
}
return result, nil
}
1 change: 1 addition & 0 deletions pkg/util/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
EnvOperatorNodeName = "MY_NODE_NAME"
EnvOperatorPodName = "MY_POD_NAME"
EnvOperatorPodNamespace = "MY_POD_NAMESPACE"
EnvOperatorPodIP = "MY_POD_IP"

EnvArangodJWTSecret = "ARANGOD_JWT_SECRET" // Contains JWT secret for the ArangoDB cluster
EnvArangoSyncJWTSecret = "ARANGOSYNC_JWT_SECRET" // Contains JWT secret for the ArangoSync masters
Expand Down