From e862da4001788a5270c58b1c686da7148f72da8c Mon Sep 17 00:00:00 2001 From: Nick Sardo Date: Tue, 5 Jun 2018 17:30:41 -0700 Subject: [PATCH] Add echo server --- Dockerfile.echo | 18 ++++++ Makefile | 2 +- cmd/echo/app/env.go | 35 ++++++++++ cmd/echo/app/flags.go | 41 ++++++++++++ cmd/echo/app/handlers.go | 108 +++++++++++++++++++++++++++++++ cmd/echo/app/tls.go | 103 +++++++++++++++++++++++++++++ cmd/echo/main.go | 31 +++++++++ deploy/echo/yaml/deployment.yaml | 42 ++++++++++++ deploy/echo/yaml/service.yaml | 21 ++++++ 9 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.echo create mode 100644 cmd/echo/app/env.go create mode 100644 cmd/echo/app/flags.go create mode 100644 cmd/echo/app/handlers.go create mode 100644 cmd/echo/app/tls.go create mode 100644 cmd/echo/main.go create mode 100644 deploy/echo/yaml/deployment.yaml create mode 100644 deploy/echo/yaml/service.yaml diff --git a/Dockerfile.echo b/Dockerfile.echo new file mode 100644 index 0000000000..c407bb3013 --- /dev/null +++ b/Dockerfile.echo @@ -0,0 +1,18 @@ +# Copyright 2018 The Kubernetes Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM debian:9 + +ADD bin/ARG_ARCH/ARG_BIN /ARG_BIN +ENTRYPOINT ["/ARG_BIN"] diff --git a/Makefile b/Makefile index 987a1eb140..e1d99071e3 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ PKG := k8s.io/ingress-gce # List of binaries to build. You must have a matching Dockerfile.BINARY # for each BINARY. -CONTAINER_BINARIES := glbc e2e-test +CONTAINER_BINARIES := glbc e2e-test echo # Latest commit hash for current branch. GIT_COMMIT := $(shell git rev-parse HEAD) diff --git a/cmd/echo/app/env.go b/cmd/echo/app/env.go new file mode 100644 index 0000000000..ccc68de074 --- /dev/null +++ b/cmd/echo/app/env.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import "os" + +type Env struct { + Node string + Pod string + Namespace string +} + +var ( + E = Env{} +) + +func init() { + E.Node = os.Getenv("node") + E.Pod = os.Getenv("pod") + E.Namespace = os.Getenv("namespace") +} diff --git a/cmd/echo/app/flags.go b/cmd/echo/app/flags.go new file mode 100644 index 0000000000..fd4b234fea --- /dev/null +++ b/cmd/echo/app/flags.go @@ -0,0 +1,41 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import ( + "flag" + "time" +) + +const ( + DefaultCertLifeSpan = 24 * time.Hour * 365 +) + +var ( + F = struct { + CertificateLifeSpan time.Duration + HTTPPort int + HTTPSPort int + }{} +) + +// RegisterFlags creates flags. +func RegisterFlags() { + flag.DurationVar(&F.CertificateLifeSpan, "cert-duration", DefaultCertLifeSpan, "The lifespan of the TLS certificate created on binary start") + flag.IntVar(&F.HTTPPort, "http-port", 8080, "Port use for HTTP, 0 will disable this protocol") + flag.IntVar(&F.HTTPSPort, "https-port", 8443, "Port use for HTTPS, 0 will disable this protocol") +} diff --git a/cmd/echo/app/handlers.go b/cmd/echo/app/handlers.go new file mode 100644 index 0000000000..2ee64e738a --- /dev/null +++ b/cmd/echo/app/handlers.go @@ -0,0 +1,108 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/golang/glog" +) + +const ( + serverIdleTimeout = 620 * time.Second +) + +// RunHTTPServer runs HTTP and HTTPS goroutines and blocks. +func RunHTTPServer(ctx context.Context) { + http.HandleFunc("/healthcheck", healthCheck) + http.HandleFunc("/", echo) + + go func() { + if F.HTTPSPort == 0 { + return + } + + server := &http.Server{Addr: fmt.Sprintf(":%d", F.HTTPSPort), IdleTimeout: serverIdleTimeout} + cert, key := createCert() + err := server.ListenAndServeTLS(cert, key) + if err != nil { + glog.Fatal(err) + } + + <-ctx.Done() + server.Shutdown(ctx) + }() + + go func() { + if F.HTTPPort == 0 { + return + } + + server := &http.Server{Addr: fmt.Sprintf(":%d", F.HTTPPort), IdleTimeout: serverIdleTimeout} + err := server.ListenAndServe() + if err != nil { + glog.Fatal(err) + } + + <-ctx.Done() + server.Shutdown(ctx) + }() + + <-ctx.Done() +} + +func healthCheck(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("health: OK")) + glog.V(3).Infof("healthcheck: %v, %v, %v", time.Now(), r.UserAgent(), r.RemoteAddr) +} + +func echo(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + var dump = struct { + Method string `json:"method"` + URI string `json:"uri"` + HTTPVersion string `json:"httpVersion"` + K8sEnv Env `json:"k8sEnv"` + RemoteAddr string `json:"remoteAddr"` + TLS bool `json:"tls"` + Header map[string][]string `json:"header"` + }{ + Method: r.Method, + URI: r.RequestURI, + HTTPVersion: fmt.Sprintf("%d.%d", r.ProtoMajor, r.ProtoMinor), + K8sEnv: E, + RemoteAddr: r.RemoteAddr, + Header: r.Header, + TLS: r.TLS != nil, + } + + dumpData, err := json.MarshalIndent(dump, "", "\t") + if err != nil { + glog.Errorf("failed to marshal dump: %v", err) + return + } + + w.Write(dumpData) + glog.V(3).Infof("echo: %v, %v, %v", time.Now(), r.UserAgent(), r.RemoteAddr) +} diff --git a/cmd/echo/app/tls.go b/cmd/echo/app/tls.go new file mode 100644 index 0000000000..bedbecf483 --- /dev/null +++ b/cmd/echo/app/tls.go @@ -0,0 +1,103 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "time" + + "github.com/golang/glog" +) + +// createCert creates a certificate and key in temporary files and returns their paths. +func createCert() (certFilePath string, keyFilepath string) { + cert, key, err := generateInsecureCertAndKey("echo", time.Now(), F.CertificateLifeSpan) + if err != nil { + glog.Fatal(err) + } + + tmpCert, err := ioutil.TempFile("", "server.crt") + if err != nil { + glog.Fatal(err) + } + + tmpKey, err := ioutil.TempFile("", "server.key") + if err != nil { + glog.Fatal(err) + } + + if err := ioutil.WriteFile(tmpCert.Name(), cert, 0644); err != nil { + glog.Fatal(err) + } + + if err := ioutil.WriteFile(tmpKey.Name(), key, 0644); err != nil { + glog.Fatal(err) + } + + return tmpCert.Name(), tmpKey.Name() +} + +const rsaBits = 2048 + +// https://golang.org/src/crypto/tls/generate_cert.go +func generateInsecureCertAndKey(organization string, validFrom time.Time, validFor time.Duration) (cert, key []byte, err error) { + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + glog.Fatalf("failed to generate serial number: %s", err) + } + + validUntill := validFrom.Add(validFor) + + priv, err := rsa.GenerateKey(rand.Reader, rsaBits) + if err != nil { + glog.Fatalf("failed to generate private key: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{organization}, + }, + NotBefore: validFrom, + NotAfter: validUntill, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + glog.Fatalf("Failed to create certificate: %s", err) + } + var certBytes bytes.Buffer + pem.Encode(&certBytes, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + + var keyBytes bytes.Buffer + pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)} + pem.Encode(&keyBytes, pb) + + return certBytes.Bytes(), keyBytes.Bytes(), nil +} diff --git a/cmd/echo/main.go b/cmd/echo/main.go new file mode 100644 index 0000000000..bbb05aabda --- /dev/null +++ b/cmd/echo/main.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "flag" + + "k8s.io/ingress-gce/cmd/echo/app" +) + +func main() { + app.RegisterFlags() + flag.Parse() + + app.RunHTTPServer(context.Background()) +} diff --git a/deploy/echo/yaml/deployment.yaml b/deploy/echo/yaml/deployment.yaml new file mode 100644 index 0000000000..34c78678fb --- /dev/null +++ b/deploy/echo/yaml/deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: echo +spec: + replicas: 1 + template: + metadata: + labels: + app: echo + spec: + containers: + - name: echo + image: [YOUR REGISTRY]/echoserver:latest + env: + - name: namespace + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: pod + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: node + valueFrom: + fieldRef: + fieldPath: spec.nodeName + ports: + - name: http + containerPort: 8080 + - name: https + containerPort: 8443 + readinessProbe: + httpGet: + path: /healthcheck + scheme: HTTP + port: http + livenessProbe: + httpGet: + path: /healthcheck + scheme: HTTP + port: http diff --git a/deploy/echo/yaml/service.yaml b/deploy/echo/yaml/service.yaml new file mode 100644 index 0000000000..d76f95d955 --- /dev/null +++ b/deploy/echo/yaml/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + service.alpha.kubernetes.io/app-protocols: '{"https":"HTTPS","http":"HTTP"}' + name: echo + labels: + app: echo +spec: + type: NodePort + ports: + - port: 80 + protocol: TCP + name: http + targetPort: http + - port: 443 + protocol: TCP + name: https + targetPort: https + selector: + app: echo