From e08e6da775f56a0c8eff33a3e6bbedaa48182abc Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 20:40:10 +0100 Subject: [PATCH 01/14] Initial Commit --- .gitignore | 2 + arista.proto | 24 ++++++++ grpc.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++ main.go | 73 +++++++++++++++++++++++ pki.go | 88 ++++++++++++++++++++++++++++ pki/.gitignore | 3 + pki/clean.sh | 6 ++ pki/gen.sh | 7 +++ proto/.gitignore | 1 + proto/generate.go | 3 + service.go | 54 +++++++++++++++++ 11 files changed, 404 insertions(+) create mode 100644 .gitignore create mode 100644 arista.proto create mode 100644 grpc.go create mode 100644 main.go create mode 100644 pki.go create mode 100644 pki/.gitignore create mode 100755 pki/clean.sh create mode 100755 pki/gen.sh create mode 100644 proto/.gitignore create mode 100644 proto/generate.go create mode 100644 service.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..045c22e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +arista-proxy +*swp diff --git a/arista.proto b/arista.proto new file mode 100644 index 0000000..75fc1f3 --- /dev/null +++ b/arista.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package proto; + +message ShowVersionRequest { +}; + +message ShowVersionResponse { + string model_name = 1; + string internal_version = 2; + string system_mac_address = 3; + string serial_number = 4; + int64 mem_total = 5; + double bootup_timestamp = 6; + int64 mem_free = 7; + string version = 8; + string architecture = 9; + string internal_build_id = 10; + string hardware_revision = 11; +}; + +service AristaProxy { + rpc ShowVersion(ShowVersionRequest) returns (ShowVersionResponse); +}; diff --git a/grpc.go b/grpc.go new file mode 100644 index 0000000..ddc978f --- /dev/null +++ b/grpc.go @@ -0,0 +1,143 @@ +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "net/http" + + "github.com/golang/glog" + "github.com/q3k/statusz" + "golang.org/x/net/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/reflection" + + pb "code.hackerspace.pl/q3k/arista-proxy/proto" +) + +type serverOpts struct { + listenAddress string + debugAddress string + tlsCAPath string + tlsCertificatePath string + tlsKeyPath string + pkiRealm string +} + +type server struct { + arista *aristaClient + opts *serverOpts + + grpc struct { + listen net.Listener + server *grpc.Server + } + http struct { + listen net.Listener + server *http.Server + } +} + +func newServer(opts *serverOpts, arista *aristaClient) (*server, error) { + return &server{ + opts: opts, + arista: arista, + }, nil +} + +func (s *server) trace(ctx context.Context, f string, args ...interface{}) { + tr, ok := trace.FromContext(ctx) + if !ok { + fmtd := fmt.Sprintf(f, args...) + glog.Warningf("No trace in %v: %s", ctx, fmtd) + return + } + tr.LazyPrintf(f, args...) +} + +func (s *server) setupGRPC(options ...grpc.ServerOption) error { + serverCert, err := tls.LoadX509KeyPair(s.opts.tlsCertificatePath, s.opts.tlsKeyPath) + if err != nil { + return fmt.Errorf("while loading keypair: %v", err) + } + + certPool := x509.NewCertPool() + ca, err := ioutil.ReadFile(s.opts.tlsCAPath) + if err != nil { + return fmt.Errorf("while loading ca certificate: %v", err) + } + if ok := certPool.AppendCertsFromPEM(ca); !ok { + return fmt.Errorf("while appending ca certificate to pool: %v", err) + } + + lis, err := net.Listen("tcp", s.opts.listenAddress) + if err != nil { + return fmt.Errorf("while listening on main port: %v", err) + } + + creds := credentials.NewTLS(&tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{serverCert}, + ClientCAs: certPool, + }) + + s.grpc.listen = lis + options = append([]grpc.ServerOption{grpc.Creds(creds)}, options...) + s.grpc.server = grpc.NewServer(options...) + + return nil +} + +func (s *server) setupDebugHTTP(mux http.Handler) error { + lis, err := net.Listen("tcp", s.opts.debugAddress) + if err != nil { + return fmt.Errorf("while listening on main port: %v", err) + } + + s.http.listen = lis + s.http.server = &http.Server{ + Addr: s.opts.debugAddress, + Handler: mux, + } + + return nil +} + +func (s *server) serveForever() { + grpc.EnableTracing = true + + if err := s.setupGRPC(grpc.UnaryInterceptor(s.unaryInterceptor)); err != nil { + glog.Exitf("Could not setup GRPC server: %v", err) + } + pb.RegisterAristaProxyServer(s.grpc.server, s) + reflection.Register(s.grpc.server) + + go func() { + if err := s.grpc.server.Serve(s.grpc.listen); err != nil { + glog.Exitf("Could not start GRPC server: %v", err) + } + }() + glog.Infof("Listening for GRPC on %v", s.opts.listenAddress) + + httpMux := http.NewServeMux() + httpMux.HandleFunc("/debug/status", statusz.StatusHandler) + httpMux.HandleFunc("/debug/requests", trace.Traces) + httpMux.HandleFunc("/", statusz.StatusHandler) + + if err := s.setupDebugHTTP(httpMux); err != nil { + glog.Exitf("Could not setup HTTP server: %v", err) + } + + go func() { + if err := s.http.server.Serve(s.http.listen); err != nil { + glog.Exitf("Could not start HTTP server: %v", err) + } + }() + glog.Infof("Listening for HTTP on %v", s.opts.debugAddress) + + select {} +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6c68977 --- /dev/null +++ b/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/golang/glog" + "github.com/ybbus/jsonrpc" +) + +var ( + flagAristaAPI string + flagListenAddress string + flagDebugAddress string + flagCAPath string + flagCertificatePath string + flagKeyPath string + flagPKIRealm string +) + +type aristaClient struct { + rpc jsonrpc.RPCClient +} + +func (c *aristaClient) structuredCall(res interface{}, command ...string) error { + cmd := struct { + Version int `json:"version"` + Cmds []string `json:"cmds"` + Format string `json:"format"` + }{ + Version: 1, + Cmds: command, + Format: "json", + } + + err := c.rpc.CallFor(res, "runCmds", cmd) + if err != nil { + return fmt.Errorf("could not execute structured call: %v", err) + } + return nil +} + +func main() { + flag.StringVar(&flagAristaAPI, "arista_api", "http://admin:password@1.2.3.4:80/command-api", "Arista remote endpoint") + flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:8080", "gRPC listen address") + flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:8081", "Debug HTTP listen address, or empty to disable") + flag.StringVar(&flagCAPath, "tls_ca_path", "pki/ca.pem", "Path to PKI CA certificate") + flag.StringVar(&flagCertificatePath, "tls_certificate_path", "pki/service.pem", "Path to PKI service certificate") + flag.StringVar(&flagKeyPath, "tls_key_path", "pki/service-key.pem", "Path to PKI service private key") + flag.StringVar(&flagPKIRealm, "pki_realm", "svc.cluster.local", "PKI realm") + flag.Set("logtostderr", "true") + flag.Parse() + + arista := &aristaClient{ + rpc: jsonrpc.NewClient(flagAristaAPI), + } + + opts := &serverOpts{ + listenAddress: flagListenAddress, + debugAddress: flagDebugAddress, + tlsCAPath: flagCAPath, + tlsCertificatePath: flagCertificatePath, + tlsKeyPath: flagKeyPath, + pkiRealm: flagPKIRealm, + } + server, err := newServer(opts, arista) + if err != nil { + glog.Errorf("Could not create server: %v", err) + } + + glog.Info("Starting up...") + server.serveForever() +} diff --git a/pki.go b/pki.go new file mode 100644 index 0000000..fdb4e34 --- /dev/null +++ b/pki.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "golang.org/x/net/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" +) + +type clientPKIInfo struct { + realm string + principal string + job string +} + +func (c *clientPKIInfo) String() string { + return fmt.Sprintf("job=%q, principal=%q, realm=%q", c.job, c.principal, c.realm) +} + +func parseClientName(realm, name string) (*clientPKIInfo, error) { + if !strings.HasSuffix(name, "."+realm) { + return nil, fmt.Errorf("invalid realm") + } + service := strings.TrimSuffix(name, "."+realm) + parts := strings.Split(service, ".") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid service") + } + return &clientPKIInfo{ + realm: realm, + principal: parts[1], + job: parts[0], + }, nil +} + +const ( + ctxKeyPKIInfo = "hscloud-pki-info" +) + +func withPKIInfo(ctx context.Context, c *clientPKIInfo) context.Context { + tr, ok := trace.FromContext(ctx) + if ok { + tr.LazyPrintf("PKI Peer: %s", c.String()) + } + return context.WithValue(ctx, ctxKeyPKIInfo, c) +} + +func (s *server) unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + peer, ok := peer.FromContext(ctx) + if !ok { + s.trace(ctx, "Could not establish identity of peer.") + return nil, status.Error(codes.InvalidArgument, "no peer info") + } + + authInfo, ok := peer.AuthInfo.(credentials.TLSInfo) + if !ok { + s.trace(ctx, "Could not establish TLS identity of peer.") + return nil, status.Error(codes.InvalidArgument, "no TLS peer info") + } + + chains := authInfo.State.VerifiedChains + if len(chains) != 1 { + s.trace(ctx, "No trusted chain found.") + return nil, status.Error(codes.InvalidArgument, "invalid TLS certificate") + } + chain := chains[0] + + certDNs := make([]string, len(chain)) + for i, cert := range chain { + certDNs[i] = cert.Subject.String() + } + s.trace(ctx, "TLS chain: %s", strings.Join(certDNs, ", ")) + + clientInfo, err := parseClientName(s.opts.pkiRealm, chain[0].Subject.CommonName) + if err != nil { + s.trace(ctx, "Could not parse certificate DN: %v", err) + return nil, status.Error(codes.InvalidArgument, "invalid TLS CommonName") + } + ctx = withPKIInfo(ctx, clientInfo) + + return handler(ctx, req) +} diff --git a/pki/.gitignore b/pki/.gitignore new file mode 100644 index 0000000..6d26d49 --- /dev/null +++ b/pki/.gitignore @@ -0,0 +1,3 @@ +*csr +*pem +*json diff --git a/pki/clean.sh b/pki/clean.sh new file mode 100755 index 0000000..490223d --- /dev/null +++ b/pki/clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e -x + +rm *pem +rm *csr diff --git a/pki/gen.sh b/pki/gen.sh new file mode 100755 index 0000000..e09e9f3 --- /dev/null +++ b/pki/gen.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e -x + +test -f ca.pem || ( cfssl gencert -initca ca_csr.json | cfssljson -bare ca ) +test -f service.pem || ( cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca_config.json -profile=test service_csr.json | cfssljson -bare service ) +test -f client.pem || ( cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca_config.json -profile=test client_csr.json | cfssljson -bare client ) diff --git a/proto/.gitignore b/proto/.gitignore new file mode 100644 index 0000000..46ddcab --- /dev/null +++ b/proto/.gitignore @@ -0,0 +1 @@ +arista.pb.go diff --git a/proto/generate.go b/proto/generate.go new file mode 100644 index 0000000..92f2720 --- /dev/null +++ b/proto/generate.go @@ -0,0 +1,3 @@ +//go:generate protoc -I.. ../arista.proto --go_out=plugins=grpc:. + +package proto diff --git a/service.go b/service.go new file mode 100644 index 0000000..0010ff9 --- /dev/null +++ b/service.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + + "github.com/golang/glog" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "code.hackerspace.pl/q3k/arista-proxy/proto" +) + +func (s *server) ShowVersion(ctx context.Context, req *pb.ShowVersionRequest) (*pb.ShowVersionResponse, error) { + var version []struct { + ModelName string `json:"modelName"` + InternalVersion string `json:"internalVersion"` + SystemMacAddress string `json:"systemMacAddress"` + SerialNumber string `json:"serialNumber"` + MemTotal int64 `json:"memTotal"` + BootupTimestamp float64 `json:"bootupTimestamp"` + MemFree int64 `json:"memFree"` + Version string `json:"version"` + Architecture string `json:"architecture"` + InternalBuildId string `json:"internalBuildId"` + HardwareRevision string `json:"hardwareRevision"` + } + + err := s.arista.structuredCall(&version, "show version") + if err != nil { + glog.Errorf("EOS Capi: show version: %v", err) + return nil, status.Error(codes.Unavailable, "EOS Capi call failed") + } + + if len(version) != 1 { + glog.Errorf("Expected 1-length result, got %d", len(version)) + return nil, status.Error(codes.Internal, "Internal error") + } + + d := version[0] + + return &pb.ShowVersionResponse{ + ModelName: d.ModelName, + InternalVersion: d.InternalVersion, + SystemMacAddress: d.SystemMacAddress, + SerialNumber: d.SerialNumber, + MemTotal: d.MemTotal, + BootupTimestamp: d.BootupTimestamp, + MemFree: d.MemFree, + Version: d.Version, + Architecture: d.Architecture, + InternalBuildId: d.InternalBuildId, + HardwareRevision: d.HardwareRevision, + }, nil +} From 97810e1ee09e42f474e6f8176e4f34d737cd5144 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:00:46 +0100 Subject: [PATCH 02/14] Add README.md --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..59b8335 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +Old Shitty Arista eAPI/Capi <-> gRPC proxy +========================================== + +Our Arista 7148S does not support gRPC/OpenConfig, so we have to make our own damn gRPC proxy. + +The schema is supposed to be 1:1 mapped to the JSON-RPC EAPI. This is just a dumb proxy. + +PKI Introduction +---------------- + +This project is a testing ground for the HSCloud PKI setup. Long story short, +all gRPC is mutually authenticated via TLS (server & client certs). + +All certs for mutual auth have the following CN/SAN format: + + .. + +For example, if principal maps into a 'group' and job into a 'user': + + arista-proxy-dcr01u23.prod.c.example.com + + job = arista-proxy-dcr01u23 + principal = cluster-management-prod + realm = c.example.com + +The Realm is a DNS name that is global to all jobs that need mutual authentication. + +The Principal is any name that carries significance for logical grouping of jobs. +It can, but doesn't need to, group jobs by similar permissions. + +The Job is any name that identifies uniquely (within the principal) a security +endpoint that describes a single security policy for a gRPC endpoint. + +The entire CN should be DNS resolvable into an IP address that would respond to +gRPC requests on port 42000 (with a server TLS certificate that represents this CN) if the +job represents a service. + +This maps nicely to the Kubernetes Cluster DNS format if you set `realm` to `svc.cluster.local`. +Then, `principal` maps to a Kubernetes namespace, and `job` maps into a Kubernetes service. + + arista-proxy-dcr01u23.arista-prod.svc.cluster.local + + job/service = arista-proxy-dcr01u23 + principal/namespace = arista-prod + realm = svc.cluster.local + +ACLs based on job/principal are yet to be implemented :). + +PKI Certs for Development +------------------------- + +In production, those certs will be automatigacally provided for you by +automation. In development, you'll have to do the following: + + cd pki + ./gen.sh + +This will generate: + - `pki/ca.pem` - CA certificate + - `pki/client{,-key}.pem` - certificate and key for `developer.humans.svc.cluster.local` + - `pki/service{,-key}.pem` - certificate and key for `test.arista-proxy.svc.cluster.local` + +You will have to setup an /etc/hosts alias to make `test.arista-proxy.svc.cluster.local` resolve to your machine. + + # cat /etc/hosts + ... + 127.0.0.1 test.arista-proxy.svc.cluster.local + ... + +You can then start `arista-proxy` with default flags and talk to it via gRPC: + + ./arista-proxy + + alias grpc-dev="grpc -cacert $(pwd)/pki/ca.pem -key $(pwd)/pki/client-key.pem -cert $(pwd)/pki/client.pem" + grpc-dev test.arista-proxy.svc.cluster.local:42000 proto.AristaProxy.ShowVersion + From eb6154b1618ff2a94c490491a753437f02a57f34 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:02:21 +0100 Subject: [PATCH 03/14] Update README --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 59b8335..3074df0 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,41 @@ You can then start `arista-proxy` with default flags and talk to it via gRPC: alias grpc-dev="grpc -cacert $(pwd)/pki/ca.pem -key $(pwd)/pki/client-key.pem -cert $(pwd)/pki/client.pem" grpc-dev test.arista-proxy.svc.cluster.local:42000 proto.AristaProxy.ShowVersion +Debug Status Page +----------------- + +The `debug_address` flag controls spawning an HTTP server useful for debugging. You can use it to inspect gRPC request and view general status information of the proxy. + +Flags +----- + + ./arista-proxy -help + Usage of ./arista-proxy: + -alsologtostderr + log to standard error as well as files + -arista_api string + Arista remote endpoint (default "http://admin:password@1.2.3.4:80/command-api") + -debug_address string + Debug HTTP listen address, or empty to disable (default "127.0.0.1:30000") + -listen_address string + gRPC listen address (default "127.0.0.1:30001") + -log_backtrace_at value + when logging hits line file:N, emit a stack trace + -log_dir string + If non-empty, write log files in this directory + -logtostderr + log to standard error instead of files + -pki_realm string + PKI realm (default "svc.cluster.local") + -stderrthreshold value + logs at or above this threshold go to stderr + -tls_ca_path string + Path to PKI CA certificate (default "pki/ca.pem") + -tls_certificate_path string + Path to PKI service certificate (default "pki/service.pem") + -tls_key_path string + Path to PKI service private key (default "pki/service-key.pem") + -v value + log level for V logs + -vmodule value + comma-separated list of pattern=N settings for file-filtered logging From c3caeffbc9c28ea23f9364ae5d99a991ca1bc3a8 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:03:03 +0100 Subject: [PATCH 04/14] Change default ports --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 6c68977..deb34d5 100644 --- a/main.go +++ b/main.go @@ -42,8 +42,8 @@ func (c *aristaClient) structuredCall(res interface{}, command ...string) error func main() { flag.StringVar(&flagAristaAPI, "arista_api", "http://admin:password@1.2.3.4:80/command-api", "Arista remote endpoint") - flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:8080", "gRPC listen address") - flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:8081", "Debug HTTP listen address, or empty to disable") + flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:42000", "gRPC listen address") + flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:42001", "Debug HTTP listen address, or empty to disable") flag.StringVar(&flagCAPath, "tls_ca_path", "pki/ca.pem", "Path to PKI CA certificate") flag.StringVar(&flagCertificatePath, "tls_certificate_path", "pki/service.pem", "Path to PKI service certificate") flag.StringVar(&flagKeyPath, "tls_key_path", "pki/service-key.pem", "Path to PKI service private key") From 10d03eab4f43a12543d6cb6c96cc69b0d71b2044 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:04:35 +0100 Subject: [PATCH 05/14] Add go get/generate info to README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3074df0..12f3d85 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,13 @@ Then, `principal` maps to a Kubernetes namespace, and `job` maps into a Kubernet ACLs based on job/principal are yet to be implemented :). +Getting and Building +-------------------- + + go get code.hackerspace.pl/q3k/arista-proxy + go generate code.hackerspace.pl/q3k/arista-proxy/proto + go build code.hackerspace.pl/q3k/arista-proxy + PKI Certs for Development ------------------------- From a509a47a50ea7ba7ef95138fc8d2e02e5a2a5ece Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:06:02 +0100 Subject: [PATCH 06/14] Update flag dump in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12f3d85..fb3883d 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,9 @@ Flags -arista_api string Arista remote endpoint (default "http://admin:password@1.2.3.4:80/command-api") -debug_address string - Debug HTTP listen address, or empty to disable (default "127.0.0.1:30000") + Debug HTTP listen address, or empty to disable (default "127.0.0.1:42000") -listen_address string - gRPC listen address (default "127.0.0.1:30001") + gRPC listen address (default "127.0.0.1:43001") -log_backtrace_at value when logging hits line file:N, emit a stack trace -log_dir string From b27d528d6975eb7f89f52104cf246ecf041cc1f4 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:07:23 +0100 Subject: [PATCH 07/14] Allow disabling debug server --- grpc.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/grpc.go b/grpc.go index ddc978f..a47877e 100644 --- a/grpc.go +++ b/grpc.go @@ -123,21 +123,25 @@ func (s *server) serveForever() { }() glog.Infof("Listening for GRPC on %v", s.opts.listenAddress) - httpMux := http.NewServeMux() - httpMux.HandleFunc("/debug/status", statusz.StatusHandler) - httpMux.HandleFunc("/debug/requests", trace.Traces) - httpMux.HandleFunc("/", statusz.StatusHandler) + if s.opts.debugAddress == "" { + glog.Info("Disabling debug HTTP server") + } else { + httpMux := http.NewServeMux() + httpMux.HandleFunc("/debug/status", statusz.StatusHandler) + httpMux.HandleFunc("/debug/requests", trace.Traces) + httpMux.HandleFunc("/", statusz.StatusHandler) - if err := s.setupDebugHTTP(httpMux); err != nil { - glog.Exitf("Could not setup HTTP server: %v", err) - } - - go func() { - if err := s.http.server.Serve(s.http.listen); err != nil { - glog.Exitf("Could not start HTTP server: %v", err) + if err := s.setupDebugHTTP(httpMux); err != nil { + glog.Exitf("Could not setup HTTP server: %v", err) } - }() - glog.Infof("Listening for HTTP on %v", s.opts.debugAddress) + + go func() { + if err := s.http.server.Serve(s.http.listen); err != nil { + glog.Exitf("Could not start HTTP server: %v", err) + } + }() + glog.Infof("Listening for HTTP on %v", s.opts.debugAddress) + } select {} } From 0ca40feb954ab3d631808c4a76c8e675ce9bf6a0 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 27 Aug 2018 21:10:20 +0100 Subject: [PATCH 08/14] Add cfssl configs for dev PKI --- pki/.gitignore | 1 - pki/ca_config.json | 13 +++++++++++++ pki/ca_csr.json | 11 +++++++++++ pki/client_csr.json | 12 ++++++++++++ pki/service_csr.json | 12 ++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 pki/ca_config.json create mode 100644 pki/ca_csr.json create mode 100644 pki/client_csr.json create mode 100644 pki/service_csr.json diff --git a/pki/.gitignore b/pki/.gitignore index 6d26d49..e24607d 100644 --- a/pki/.gitignore +++ b/pki/.gitignore @@ -1,3 +1,2 @@ *csr *pem -*json diff --git a/pki/ca_config.json b/pki/ca_config.json new file mode 100644 index 0000000..113a08f --- /dev/null +++ b/pki/ca_config.json @@ -0,0 +1,13 @@ +{ + "signing": { + "default": { + "expiry": "8760h" + }, + "profiles": { + "test": { + "usages": ["signing", "key encipherment", "server auth", "client auth"], + "expiry": "8760h" + } + } + } +} diff --git a/pki/ca_csr.json b/pki/ca_csr.json new file mode 100644 index 0000000..b24c638 --- /dev/null +++ b/pki/ca_csr.json @@ -0,0 +1,11 @@ +{ + "names": [ + { + "C": "US", + "L": "San Francisco", + "O": "Internet Widgets, Inc.", + "OU": "WWW", + "ST": "California" + } + ] +} diff --git a/pki/client_csr.json b/pki/client_csr.json new file mode 100644 index 0000000..26fc041 --- /dev/null +++ b/pki/client_csr.json @@ -0,0 +1,12 @@ +{ + "CN": "developer.humans.svc.cluster.local", + "names": [ + { + "C": "US", + "L": "San Francisco", + "O": "Internet Widgets, Inc.", + "OU": "WWW", + "ST": "California" + } + ] +} diff --git a/pki/service_csr.json b/pki/service_csr.json new file mode 100644 index 0000000..72c910e --- /dev/null +++ b/pki/service_csr.json @@ -0,0 +1,12 @@ +{ + "CN": "test.arista-proxy.svc.cluster.local", + "names": [ + { + "C": "US", + "L": "San Francisco", + "O": "Internet Widgets, Inc.", + "OU": "WWW", + "ST": "California" + } + ] +} From f9d85cf58588976ec3172441b288ae2a10b8fdb7 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Tue, 28 Aug 2018 14:13:36 +0100 Subject: [PATCH 09/14] Move pki.go into code.hackerspace.pl/q3k/hspki --- grpc.go | 4 +-- main.go | 3 -- pki.go | 88 --------------------------------------------------------- 3 files changed, 2 insertions(+), 93 deletions(-) delete mode 100644 pki.go diff --git a/grpc.go b/grpc.go index a47877e..9569ee5 100644 --- a/grpc.go +++ b/grpc.go @@ -9,6 +9,7 @@ import ( "net" "net/http" + "code.hackerspace.pl/q3k/hspki" "github.com/golang/glog" "github.com/q3k/statusz" "golang.org/x/net/trace" @@ -25,7 +26,6 @@ type serverOpts struct { tlsCAPath string tlsCertificatePath string tlsKeyPath string - pkiRealm string } type server struct { @@ -110,7 +110,7 @@ func (s *server) setupDebugHTTP(mux http.Handler) error { func (s *server) serveForever() { grpc.EnableTracing = true - if err := s.setupGRPC(grpc.UnaryInterceptor(s.unaryInterceptor)); err != nil { + if err := s.setupGRPC(hspki.WithServerHSPKI()); err != nil { glog.Exitf("Could not setup GRPC server: %v", err) } pb.RegisterAristaProxyServer(s.grpc.server, s) diff --git a/main.go b/main.go index deb34d5..66e8d94 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ var ( flagCAPath string flagCertificatePath string flagKeyPath string - flagPKIRealm string ) type aristaClient struct { @@ -47,7 +46,6 @@ func main() { flag.StringVar(&flagCAPath, "tls_ca_path", "pki/ca.pem", "Path to PKI CA certificate") flag.StringVar(&flagCertificatePath, "tls_certificate_path", "pki/service.pem", "Path to PKI service certificate") flag.StringVar(&flagKeyPath, "tls_key_path", "pki/service-key.pem", "Path to PKI service private key") - flag.StringVar(&flagPKIRealm, "pki_realm", "svc.cluster.local", "PKI realm") flag.Set("logtostderr", "true") flag.Parse() @@ -61,7 +59,6 @@ func main() { tlsCAPath: flagCAPath, tlsCertificatePath: flagCertificatePath, tlsKeyPath: flagKeyPath, - pkiRealm: flagPKIRealm, } server, err := newServer(opts, arista) if err != nil { diff --git a/pki.go b/pki.go deleted file mode 100644 index fdb4e34..0000000 --- a/pki.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "context" - "fmt" - "strings" - - "golang.org/x/net/trace" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" -) - -type clientPKIInfo struct { - realm string - principal string - job string -} - -func (c *clientPKIInfo) String() string { - return fmt.Sprintf("job=%q, principal=%q, realm=%q", c.job, c.principal, c.realm) -} - -func parseClientName(realm, name string) (*clientPKIInfo, error) { - if !strings.HasSuffix(name, "."+realm) { - return nil, fmt.Errorf("invalid realm") - } - service := strings.TrimSuffix(name, "."+realm) - parts := strings.Split(service, ".") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid service") - } - return &clientPKIInfo{ - realm: realm, - principal: parts[1], - job: parts[0], - }, nil -} - -const ( - ctxKeyPKIInfo = "hscloud-pki-info" -) - -func withPKIInfo(ctx context.Context, c *clientPKIInfo) context.Context { - tr, ok := trace.FromContext(ctx) - if ok { - tr.LazyPrintf("PKI Peer: %s", c.String()) - } - return context.WithValue(ctx, ctxKeyPKIInfo, c) -} - -func (s *server) unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { - peer, ok := peer.FromContext(ctx) - if !ok { - s.trace(ctx, "Could not establish identity of peer.") - return nil, status.Error(codes.InvalidArgument, "no peer info") - } - - authInfo, ok := peer.AuthInfo.(credentials.TLSInfo) - if !ok { - s.trace(ctx, "Could not establish TLS identity of peer.") - return nil, status.Error(codes.InvalidArgument, "no TLS peer info") - } - - chains := authInfo.State.VerifiedChains - if len(chains) != 1 { - s.trace(ctx, "No trusted chain found.") - return nil, status.Error(codes.InvalidArgument, "invalid TLS certificate") - } - chain := chains[0] - - certDNs := make([]string, len(chain)) - for i, cert := range chain { - certDNs[i] = cert.Subject.String() - } - s.trace(ctx, "TLS chain: %s", strings.Join(certDNs, ", ")) - - clientInfo, err := parseClientName(s.opts.pkiRealm, chain[0].Subject.CommonName) - if err != nil { - s.trace(ctx, "Could not parse certificate DN: %v", err) - return nil, status.Error(codes.InvalidArgument, "invalid TLS CommonName") - } - ctx = withPKIInfo(ctx, clientInfo) - - return handler(ctx, req) -} From 5cb204b15e447e7fe410cf962f8c8a1ee1d9b6ed Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Tue, 28 Aug 2018 15:08:00 +0100 Subject: [PATCH 10/14] Move grpc TLS setup into hspki --- grpc.go | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/grpc.go b/grpc.go index 9569ee5..01d5634 100644 --- a/grpc.go +++ b/grpc.go @@ -2,10 +2,7 @@ package main import ( "context" - "crypto/tls" - "crypto/x509" "fmt" - "io/ioutil" "net" "net/http" @@ -14,7 +11,6 @@ import ( "github.com/q3k/statusz" "golang.org/x/net/trace" "google.golang.org/grpc" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" pb "code.hackerspace.pl/q3k/arista-proxy/proto" @@ -60,33 +56,12 @@ func (s *server) trace(ctx context.Context, f string, args ...interface{}) { } func (s *server) setupGRPC(options ...grpc.ServerOption) error { - serverCert, err := tls.LoadX509KeyPair(s.opts.tlsCertificatePath, s.opts.tlsKeyPath) - if err != nil { - return fmt.Errorf("while loading keypair: %v", err) - } - - certPool := x509.NewCertPool() - ca, err := ioutil.ReadFile(s.opts.tlsCAPath) - if err != nil { - return fmt.Errorf("while loading ca certificate: %v", err) - } - if ok := certPool.AppendCertsFromPEM(ca); !ok { - return fmt.Errorf("while appending ca certificate to pool: %v", err) - } - lis, err := net.Listen("tcp", s.opts.listenAddress) if err != nil { return fmt.Errorf("while listening on main port: %v", err) } - creds := credentials.NewTLS(&tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - Certificates: []tls.Certificate{serverCert}, - ClientCAs: certPool, - }) - s.grpc.listen = lis - options = append([]grpc.ServerOption{grpc.Creds(creds)}, options...) s.grpc.server = grpc.NewServer(options...) return nil @@ -110,7 +85,7 @@ func (s *server) setupDebugHTTP(mux http.Handler) error { func (s *server) serveForever() { grpc.EnableTracing = true - if err := s.setupGRPC(hspki.WithServerHSPKI()); err != nil { + if err := s.setupGRPC(hspki.WithServerHSPKI()...); err != nil { glog.Exitf("Could not setup GRPC server: %v", err) } pb.RegisterAristaProxyServer(s.grpc.server, s) From 543bd6fa5578acd5f1e4f4b6e3c326297afbb75b Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Tue, 28 Aug 2018 15:25:25 +0100 Subject: [PATCH 11/14] Remove pki scripts and info (now in hspki) --- README.md | 78 ++------------------------------------------ pki/.gitignore | 2 -- pki/ca_config.json | 13 -------- pki/ca_csr.json | 11 ------- pki/clean.sh | 6 ---- pki/client_csr.json | 12 ------- pki/gen.sh | 7 ---- pki/service_csr.json | 12 ------- 8 files changed, 3 insertions(+), 138 deletions(-) delete mode 100644 pki/.gitignore delete mode 100644 pki/ca_config.json delete mode 100644 pki/ca_csr.json delete mode 100755 pki/clean.sh delete mode 100644 pki/client_csr.json delete mode 100755 pki/gen.sh delete mode 100644 pki/service_csr.json diff --git a/README.md b/README.md index fb3883d..3f9ccaf 100644 --- a/README.md +++ b/README.md @@ -5,46 +5,10 @@ Our Arista 7148S does not support gRPC/OpenConfig, so we have to make our own da The schema is supposed to be 1:1 mapped to the JSON-RPC EAPI. This is just a dumb proxy. -PKI Introduction ----------------- +PKI +--- -This project is a testing ground for the HSCloud PKI setup. Long story short, -all gRPC is mutually authenticated via TLS (server & client certs). - -All certs for mutual auth have the following CN/SAN format: - - .. - -For example, if principal maps into a 'group' and job into a 'user': - - arista-proxy-dcr01u23.prod.c.example.com - - job = arista-proxy-dcr01u23 - principal = cluster-management-prod - realm = c.example.com - -The Realm is a DNS name that is global to all jobs that need mutual authentication. - -The Principal is any name that carries significance for logical grouping of jobs. -It can, but doesn't need to, group jobs by similar permissions. - -The Job is any name that identifies uniquely (within the principal) a security -endpoint that describes a single security policy for a gRPC endpoint. - -The entire CN should be DNS resolvable into an IP address that would respond to -gRPC requests on port 42000 (with a server TLS certificate that represents this CN) if the -job represents a service. - -This maps nicely to the Kubernetes Cluster DNS format if you set `realm` to `svc.cluster.local`. -Then, `principal` maps to a Kubernetes namespace, and `job` maps into a Kubernetes service. - - arista-proxy-dcr01u23.arista-prod.svc.cluster.local - - job/service = arista-proxy-dcr01u23 - principal/namespace = arista-prod - realm = svc.cluster.local - -ACLs based on job/principal are yet to be implemented :). +This service uses [HSPKI](https://code.hackerspace.pl/q3k/hspki), you will need to generate development TLS certificates for local use. Getting and Building -------------------- @@ -53,34 +17,6 @@ Getting and Building go generate code.hackerspace.pl/q3k/arista-proxy/proto go build code.hackerspace.pl/q3k/arista-proxy -PKI Certs for Development -------------------------- - -In production, those certs will be automatigacally provided for you by -automation. In development, you'll have to do the following: - - cd pki - ./gen.sh - -This will generate: - - `pki/ca.pem` - CA certificate - - `pki/client{,-key}.pem` - certificate and key for `developer.humans.svc.cluster.local` - - `pki/service{,-key}.pem` - certificate and key for `test.arista-proxy.svc.cluster.local` - -You will have to setup an /etc/hosts alias to make `test.arista-proxy.svc.cluster.local` resolve to your machine. - - # cat /etc/hosts - ... - 127.0.0.1 test.arista-proxy.svc.cluster.local - ... - -You can then start `arista-proxy` with default flags and talk to it via gRPC: - - ./arista-proxy - - alias grpc-dev="grpc -cacert $(pwd)/pki/ca.pem -key $(pwd)/pki/client-key.pem -cert $(pwd)/pki/client.pem" - grpc-dev test.arista-proxy.svc.cluster.local:42000 proto.AristaProxy.ShowVersion - Debug Status Page ----------------- @@ -105,16 +41,8 @@ Flags If non-empty, write log files in this directory -logtostderr log to standard error instead of files - -pki_realm string - PKI realm (default "svc.cluster.local") -stderrthreshold value logs at or above this threshold go to stderr - -tls_ca_path string - Path to PKI CA certificate (default "pki/ca.pem") - -tls_certificate_path string - Path to PKI service certificate (default "pki/service.pem") - -tls_key_path string - Path to PKI service private key (default "pki/service-key.pem") -v value log level for V logs -vmodule value diff --git a/pki/.gitignore b/pki/.gitignore deleted file mode 100644 index e24607d..0000000 --- a/pki/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*csr -*pem diff --git a/pki/ca_config.json b/pki/ca_config.json deleted file mode 100644 index 113a08f..0000000 --- a/pki/ca_config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "signing": { - "default": { - "expiry": "8760h" - }, - "profiles": { - "test": { - "usages": ["signing", "key encipherment", "server auth", "client auth"], - "expiry": "8760h" - } - } - } -} diff --git a/pki/ca_csr.json b/pki/ca_csr.json deleted file mode 100644 index b24c638..0000000 --- a/pki/ca_csr.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Internet Widgets, Inc.", - "OU": "WWW", - "ST": "California" - } - ] -} diff --git a/pki/clean.sh b/pki/clean.sh deleted file mode 100755 index 490223d..0000000 --- a/pki/clean.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -set -e -x - -rm *pem -rm *csr diff --git a/pki/client_csr.json b/pki/client_csr.json deleted file mode 100644 index 26fc041..0000000 --- a/pki/client_csr.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "CN": "developer.humans.svc.cluster.local", - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Internet Widgets, Inc.", - "OU": "WWW", - "ST": "California" - } - ] -} diff --git a/pki/gen.sh b/pki/gen.sh deleted file mode 100755 index e09e9f3..0000000 --- a/pki/gen.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e -x - -test -f ca.pem || ( cfssl gencert -initca ca_csr.json | cfssljson -bare ca ) -test -f service.pem || ( cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca_config.json -profile=test service_csr.json | cfssljson -bare service ) -test -f client.pem || ( cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca_config.json -profile=test client_csr.json | cfssljson -bare client ) diff --git a/pki/service_csr.json b/pki/service_csr.json deleted file mode 100644 index 72c910e..0000000 --- a/pki/service_csr.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "CN": "test.arista-proxy.svc.cluster.local", - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Internet Widgets, Inc.", - "OU": "WWW", - "ST": "California" - } - ] -} From 8123ccd61e63aac1796e10055154339b72884ee6 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Tue, 28 Aug 2018 15:37:41 +0100 Subject: [PATCH 12/14] Fix build instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f9ccaf..ed90285 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This service uses [HSPKI](https://code.hackerspace.pl/q3k/hspki), you will need Getting and Building -------------------- - go get code.hackerspace.pl/q3k/arista-proxy + go get -d -u code.hackerspace.pl/q3k/arista-proxy go generate code.hackerspace.pl/q3k/arista-proxy/proto go build code.hackerspace.pl/q3k/arista-proxy From 9cf869b8572b2e995f806ea57dbd91d08016174f Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sun, 14 Oct 2018 08:25:43 -0700 Subject: [PATCH 13/14] mirkoify, godepify --- Gopkg.lock | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 54 +++++++++++++++ grpc.go | 122 --------------------------------- main.go | 45 ++++++------ 4 files changed, 273 insertions(+), 146 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml delete mode 100644 grpc.go diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..ab31c34 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,198 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:2af0bc306f77bef606f6f6033652f0de3cc80e77008f4da52197b4b0a6be5853" + name = "code.hackerspace.pl/q3k/hspki" + packages = ["."] + pruneopts = "UT" + revision = "624295da664fea57a1e3bd8cee639ae04f4c0ccf" + +[[projects]] + branch = "master" + digest = "1:48353f64f7cc9626e4396b286552812a40a9c1d3e653d51ecbe6cedf2c4f4778" + name = "code.hackerspace.pl/q3k/mirko" + packages = ["."] + pruneopts = "UT" + revision = "b21f9b9a2ec6ada755aac41595acdf1961d3c48d" + +[[projects]] + digest = "1:e92f5581902c345eb4ceffdcd4a854fb8f73cf436d47d837d1ec98ef1fe0a214" + name = "github.com/StackExchange/wmi" + packages = ["."] + pruneopts = "UT" + revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" + version = "1.0.0" + +[[projects]] + digest = "1:64a5a67c69b70c2420e607a8545d674a23778ed9c3e80607bfd17b77c6c87f6a" + name = "github.com/go-ole/go-ole" + packages = [ + ".", + "oleutil", + ] + pruneopts = "UT" + revision = "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506" + version = "v1.2.1" + +[[projects]] + branch = "master" + digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" + name = "github.com/golang/glog" + packages = ["."] + pruneopts = "UT" + revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" + +[[projects]] + digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" + name = "github.com/golang/protobuf" + packages = [ + "proto", + "protoc-gen-go/descriptor", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/timestamp", + ] + pruneopts = "UT" + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" + +[[projects]] + branch = "master" + digest = "1:a7cc4447e0c2432f217fd2bed937c100ec475272a7a9306477170c2934297357" + name = "github.com/q3k/statusz" + packages = ["."] + pruneopts = "UT" + revision = "924f04ea71149b75f5b10f426cc4c39d6d90f6f2" + +[[projects]] + branch = "master" + digest = "1:8f042d4b4c48d38d7a1201096299bd26d044c97c032ab20ee171fe5b0d201d2e" + name = "github.com/shirou/gopsutil" + packages = [ + "internal/common", + "load", + ] + pruneopts = "UT" + revision = "a11c78ba2c13c5b1ee59c53296ba35f92f0ce658" + +[[projects]] + digest = "1:564b31f09a21367ada5b019d8413afe99cf770775606f94c7bef42b2ef79dfdf" + name = "github.com/ybbus/jsonrpc" + packages = ["."] + pruneopts = "UT" + revision = "dd866631e904a5df2067d934985c5def68f391ac" + version = "v2.1.2" + +[[projects]] + branch = "master" + digest = "1:505dbee0833715a72a529bb57c354826ad42a4496fad787fa143699b4de1a6d0" + name = "golang.org/x/net" + packages = [ + "context", + "http/httpguts", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "trace", + ] + pruneopts = "UT" + revision = "49bb7cea24b1df9410e1712aa6433dae904ff66a" + +[[projects]] + branch = "master" + digest = "1:f5aa274a0377f85735edc7fedfb0811d3cbc20af91633797cb359e29c3272271" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "UT" + revision = "fa43e7bc11baaae89f3f902b2b4d832b68234844" + +[[projects]] + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" + name = "golang.org/x/text" + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + ] + pruneopts = "UT" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + branch = "master" + digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" + name = "google.golang.org/genproto" + packages = ["googleapis/rpc/status"] + pruneopts = "UT" + revision = "af9cb2a35e7f169ec875002c1829c9b315cddc04" + +[[projects]] + digest = "1:dc0c170b110c22d9a4eccf08ab58490608053eac450bf456f3aaf9b30a668781" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclog", + "internal", + "internal/backoff", + "internal/channelz", + "internal/envconfig", + "internal/grpcrand", + "internal/transport", + "keepalive", + "metadata", + "naming", + "peer", + "reflection", + "reflection/grpc_reflection_v1alpha", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + ] + pruneopts = "UT" + revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" + version = "v1.15.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "code.hackerspace.pl/q3k/mirko", + "github.com/golang/glog", + "github.com/golang/protobuf/proto", + "github.com/ybbus/jsonrpc", + "golang.org/x/net/context", + "google.golang.org/grpc", + "google.golang.org/grpc/codes", + "google.golang.org/grpc/status", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..749416d --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,54 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "code.hackerspace.pl/q3k/mirko" + +[[constraint]] + branch = "master" + name = "github.com/golang/glog" + +[[constraint]] + name = "github.com/golang/protobuf" + version = "1.2.0" + +[[constraint]] + name = "github.com/ybbus/jsonrpc" + version = "2.1.2" + +[[constraint]] + branch = "master" + name = "golang.org/x/net" + +[[constraint]] + name = "google.golang.org/grpc" + version = "1.15.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/grpc.go b/grpc.go deleted file mode 100644 index 01d5634..0000000 --- a/grpc.go +++ /dev/null @@ -1,122 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net" - "net/http" - - "code.hackerspace.pl/q3k/hspki" - "github.com/golang/glog" - "github.com/q3k/statusz" - "golang.org/x/net/trace" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" - - pb "code.hackerspace.pl/q3k/arista-proxy/proto" -) - -type serverOpts struct { - listenAddress string - debugAddress string - tlsCAPath string - tlsCertificatePath string - tlsKeyPath string -} - -type server struct { - arista *aristaClient - opts *serverOpts - - grpc struct { - listen net.Listener - server *grpc.Server - } - http struct { - listen net.Listener - server *http.Server - } -} - -func newServer(opts *serverOpts, arista *aristaClient) (*server, error) { - return &server{ - opts: opts, - arista: arista, - }, nil -} - -func (s *server) trace(ctx context.Context, f string, args ...interface{}) { - tr, ok := trace.FromContext(ctx) - if !ok { - fmtd := fmt.Sprintf(f, args...) - glog.Warningf("No trace in %v: %s", ctx, fmtd) - return - } - tr.LazyPrintf(f, args...) -} - -func (s *server) setupGRPC(options ...grpc.ServerOption) error { - lis, err := net.Listen("tcp", s.opts.listenAddress) - if err != nil { - return fmt.Errorf("while listening on main port: %v", err) - } - - s.grpc.listen = lis - s.grpc.server = grpc.NewServer(options...) - - return nil -} - -func (s *server) setupDebugHTTP(mux http.Handler) error { - lis, err := net.Listen("tcp", s.opts.debugAddress) - if err != nil { - return fmt.Errorf("while listening on main port: %v", err) - } - - s.http.listen = lis - s.http.server = &http.Server{ - Addr: s.opts.debugAddress, - Handler: mux, - } - - return nil -} - -func (s *server) serveForever() { - grpc.EnableTracing = true - - if err := s.setupGRPC(hspki.WithServerHSPKI()...); err != nil { - glog.Exitf("Could not setup GRPC server: %v", err) - } - pb.RegisterAristaProxyServer(s.grpc.server, s) - reflection.Register(s.grpc.server) - - go func() { - if err := s.grpc.server.Serve(s.grpc.listen); err != nil { - glog.Exitf("Could not start GRPC server: %v", err) - } - }() - glog.Infof("Listening for GRPC on %v", s.opts.listenAddress) - - if s.opts.debugAddress == "" { - glog.Info("Disabling debug HTTP server") - } else { - httpMux := http.NewServeMux() - httpMux.HandleFunc("/debug/status", statusz.StatusHandler) - httpMux.HandleFunc("/debug/requests", trace.Traces) - httpMux.HandleFunc("/", statusz.StatusHandler) - - if err := s.setupDebugHTTP(httpMux); err != nil { - glog.Exitf("Could not setup HTTP server: %v", err) - } - - go func() { - if err := s.http.server.Serve(s.http.listen); err != nil { - glog.Exitf("Could not start HTTP server: %v", err) - } - }() - glog.Infof("Listening for HTTP on %v", s.opts.debugAddress) - } - - select {} -} diff --git a/main.go b/main.go index 66e8d94..f417f1f 100644 --- a/main.go +++ b/main.go @@ -4,17 +4,15 @@ import ( "flag" "fmt" + "code.hackerspace.pl/q3k/mirko" "github.com/golang/glog" "github.com/ybbus/jsonrpc" + + pb "code.hackerspace.pl/q3k/arista-proxy/proto" ) var ( - flagAristaAPI string - flagListenAddress string - flagDebugAddress string - flagCAPath string - flagCertificatePath string - flagKeyPath string + flagAristaAPI string ) type aristaClient struct { @@ -39,32 +37,31 @@ func (c *aristaClient) structuredCall(res interface{}, command ...string) error return nil } +type server struct { + arista *aristaClient +} + func main() { flag.StringVar(&flagAristaAPI, "arista_api", "http://admin:password@1.2.3.4:80/command-api", "Arista remote endpoint") - flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:42000", "gRPC listen address") - flag.StringVar(&flagDebugAddress, "debug_address", "127.0.0.1:42001", "Debug HTTP listen address, or empty to disable") - flag.StringVar(&flagCAPath, "tls_ca_path", "pki/ca.pem", "Path to PKI CA certificate") - flag.StringVar(&flagCertificatePath, "tls_certificate_path", "pki/service.pem", "Path to PKI service certificate") - flag.StringVar(&flagKeyPath, "tls_key_path", "pki/service-key.pem", "Path to PKI service private key") - flag.Set("logtostderr", "true") flag.Parse() arista := &aristaClient{ rpc: jsonrpc.NewClient(flagAristaAPI), } - opts := &serverOpts{ - listenAddress: flagListenAddress, - debugAddress: flagDebugAddress, - tlsCAPath: flagCAPath, - tlsCertificatePath: flagCertificatePath, - tlsKeyPath: flagKeyPath, - } - server, err := newServer(opts, arista) - if err != nil { - glog.Errorf("Could not create server: %v", err) + m := mirko.New() + if err := m.Listen(); err != nil { + glog.Exitf("Listen(): %v", err) } - glog.Info("Starting up...") - server.serveForever() + s := &server{ + arista: arista, + } + pb.RegisterAristaProxyServer(m.GRPC(), s) + + if err := m.Serve(); err != nil { + glog.Exitf("Serve(): %v", err) + } + + select {} } From 8bdee5bceceea73fda47454d0c4c351a0d1febc1 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Thu, 25 Oct 2018 12:05:51 +0100 Subject: [PATCH 14/14] monorepoization: move everything into arista-proxy/ --- Gopkg.lock | 198 ---------------------- Gopkg.toml | 54 ------ README.md => arista-proxy/README.md | 0 arista.proto => arista-proxy/arista.proto | 7 + main.go => arista-proxy/main.go | 0 {proto => arista-proxy/proto}/.gitignore | 0 {proto => arista-proxy/proto}/generate.go | 0 service.go => arista-proxy/service.go | 43 +++++ 8 files changed, 50 insertions(+), 252 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml rename README.md => arista-proxy/README.md (100%) rename arista.proto => arista-proxy/arista.proto (71%) rename main.go => arista-proxy/main.go (100%) rename {proto => arista-proxy/proto}/.gitignore (100%) rename {proto => arista-proxy/proto}/generate.go (100%) rename service.go => arista-proxy/service.go (50%) diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index ab31c34..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,198 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:2af0bc306f77bef606f6f6033652f0de3cc80e77008f4da52197b4b0a6be5853" - name = "code.hackerspace.pl/q3k/hspki" - packages = ["."] - pruneopts = "UT" - revision = "624295da664fea57a1e3bd8cee639ae04f4c0ccf" - -[[projects]] - branch = "master" - digest = "1:48353f64f7cc9626e4396b286552812a40a9c1d3e653d51ecbe6cedf2c4f4778" - name = "code.hackerspace.pl/q3k/mirko" - packages = ["."] - pruneopts = "UT" - revision = "b21f9b9a2ec6ada755aac41595acdf1961d3c48d" - -[[projects]] - digest = "1:e92f5581902c345eb4ceffdcd4a854fb8f73cf436d47d837d1ec98ef1fe0a214" - name = "github.com/StackExchange/wmi" - packages = ["."] - pruneopts = "UT" - revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" - version = "1.0.0" - -[[projects]] - digest = "1:64a5a67c69b70c2420e607a8545d674a23778ed9c3e80607bfd17b77c6c87f6a" - name = "github.com/go-ole/go-ole" - packages = [ - ".", - "oleutil", - ] - pruneopts = "UT" - revision = "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506" - version = "v1.2.1" - -[[projects]] - branch = "master" - digest = "1:1ba1d79f2810270045c328ae5d674321db34e3aae468eb4233883b473c5c0467" - name = "github.com/golang/glog" - packages = ["."] - pruneopts = "UT" - revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" - -[[projects]] - digest = "1:5d1b5a25486fc7d4e133646d834f6fca7ba1cef9903d40e7aa786c41b89e9e91" - name = "github.com/golang/protobuf" - packages = [ - "proto", - "protoc-gen-go/descriptor", - "ptypes", - "ptypes/any", - "ptypes/duration", - "ptypes/timestamp", - ] - pruneopts = "UT" - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" - -[[projects]] - branch = "master" - digest = "1:a7cc4447e0c2432f217fd2bed937c100ec475272a7a9306477170c2934297357" - name = "github.com/q3k/statusz" - packages = ["."] - pruneopts = "UT" - revision = "924f04ea71149b75f5b10f426cc4c39d6d90f6f2" - -[[projects]] - branch = "master" - digest = "1:8f042d4b4c48d38d7a1201096299bd26d044c97c032ab20ee171fe5b0d201d2e" - name = "github.com/shirou/gopsutil" - packages = [ - "internal/common", - "load", - ] - pruneopts = "UT" - revision = "a11c78ba2c13c5b1ee59c53296ba35f92f0ce658" - -[[projects]] - digest = "1:564b31f09a21367ada5b019d8413afe99cf770775606f94c7bef42b2ef79dfdf" - name = "github.com/ybbus/jsonrpc" - packages = ["."] - pruneopts = "UT" - revision = "dd866631e904a5df2067d934985c5def68f391ac" - version = "v2.1.2" - -[[projects]] - branch = "master" - digest = "1:505dbee0833715a72a529bb57c354826ad42a4496fad787fa143699b4de1a6d0" - name = "golang.org/x/net" - packages = [ - "context", - "http/httpguts", - "http2", - "http2/hpack", - "idna", - "internal/timeseries", - "trace", - ] - pruneopts = "UT" - revision = "49bb7cea24b1df9410e1712aa6433dae904ff66a" - -[[projects]] - branch = "master" - digest = "1:f5aa274a0377f85735edc7fedfb0811d3cbc20af91633797cb359e29c3272271" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "UT" - revision = "fa43e7bc11baaae89f3f902b2b4d832b68234844" - -[[projects]] - digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" - name = "golang.org/x/text" - packages = [ - "collate", - "collate/build", - "internal/colltab", - "internal/gen", - "internal/tag", - "internal/triegen", - "internal/ucd", - "language", - "secure/bidirule", - "transform", - "unicode/bidi", - "unicode/cldr", - "unicode/norm", - "unicode/rangetable", - ] - pruneopts = "UT" - revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" - version = "v0.3.0" - -[[projects]] - branch = "master" - digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" - name = "google.golang.org/genproto" - packages = ["googleapis/rpc/status"] - pruneopts = "UT" - revision = "af9cb2a35e7f169ec875002c1829c9b315cddc04" - -[[projects]] - digest = "1:dc0c170b110c22d9a4eccf08ab58490608053eac450bf456f3aaf9b30a668781" - name = "google.golang.org/grpc" - packages = [ - ".", - "balancer", - "balancer/base", - "balancer/roundrobin", - "codes", - "connectivity", - "credentials", - "encoding", - "encoding/proto", - "grpclog", - "internal", - "internal/backoff", - "internal/channelz", - "internal/envconfig", - "internal/grpcrand", - "internal/transport", - "keepalive", - "metadata", - "naming", - "peer", - "reflection", - "reflection/grpc_reflection_v1alpha", - "resolver", - "resolver/dns", - "resolver/passthrough", - "stats", - "status", - "tap", - ] - pruneopts = "UT" - revision = "8dea3dc473e90c8179e519d91302d0597c0ca1d1" - version = "v1.15.0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "code.hackerspace.pl/q3k/mirko", - "github.com/golang/glog", - "github.com/golang/protobuf/proto", - "github.com/ybbus/jsonrpc", - "golang.org/x/net/context", - "google.golang.org/grpc", - "google.golang.org/grpc/codes", - "google.golang.org/grpc/status", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 749416d..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,54 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - branch = "master" - name = "code.hackerspace.pl/q3k/mirko" - -[[constraint]] - branch = "master" - name = "github.com/golang/glog" - -[[constraint]] - name = "github.com/golang/protobuf" - version = "1.2.0" - -[[constraint]] - name = "github.com/ybbus/jsonrpc" - version = "2.1.2" - -[[constraint]] - branch = "master" - name = "golang.org/x/net" - -[[constraint]] - name = "google.golang.org/grpc" - version = "1.15.0" - -[prune] - go-tests = true - unused-packages = true diff --git a/README.md b/arista-proxy/README.md similarity index 100% rename from README.md rename to arista-proxy/README.md diff --git a/arista.proto b/arista-proxy/arista.proto similarity index 71% rename from arista.proto rename to arista-proxy/arista.proto index 75fc1f3..d6bf105 100644 --- a/arista.proto +++ b/arista-proxy/arista.proto @@ -19,6 +19,13 @@ message ShowVersionResponse { string hardware_revision = 11; }; +message ShowEnvironmentTemperatureRequest { +}; + +message ShowEnvironmentTemperatureResponse { +}; + service AristaProxy { rpc ShowVersion(ShowVersionRequest) returns (ShowVersionResponse); + rpc ShowEnvironmentTemperature(ShowEnvironmentTemperatureRequest) returns (ShowEnvironmentTemperatureResponse); }; diff --git a/main.go b/arista-proxy/main.go similarity index 100% rename from main.go rename to arista-proxy/main.go diff --git a/proto/.gitignore b/arista-proxy/proto/.gitignore similarity index 100% rename from proto/.gitignore rename to arista-proxy/proto/.gitignore diff --git a/proto/generate.go b/arista-proxy/proto/generate.go similarity index 100% rename from proto/generate.go rename to arista-proxy/proto/generate.go diff --git a/service.go b/arista-proxy/service.go similarity index 50% rename from service.go rename to arista-proxy/service.go index 0010ff9..62d68ea 100644 --- a/service.go +++ b/arista-proxy/service.go @@ -52,3 +52,46 @@ func (s *server) ShowVersion(ctx context.Context, req *pb.ShowVersionRequest) (* HardwareRevision: d.HardwareRevision, }, nil } + +type temperatureSensor struct { + InAlertState bool `json:"inAlertState"` + MaxTemperature float64 `json:"maxTemperature"` + RelPos int64 `json:"relPos"` + Description string `json:"description"` + Name string `json:"name"` + AlertCount int64 `json:"alertCount"` + CurrentTemperature float64 `json:"currentTemperature"` + OverheatThreshold float64 `json:"overheatThreshold"` + CriticalThreshold float64 `json:"criticalThreshold"` + HwStatus string `json:"hwStatus"` +} + +func (s *server) ShowEnvironmentTemperature(ctx context.Context, req *pb.ShowEnvironmentTemperatureRequest) (*pb.ShowEnvironmentTemperatureResponse, error) { + var response []struct { + PowerSuppplySlots []struct { + TempSensors []temperatureSensor `json:"tempSensors"` + EntPhysicalClass string `json:"entPhysicalClass"` + RelPos int64 `json:"relPos"` + } `json:"powerSupplySlots"` + + ShutdownOnOverheat bool `json:"shutdownOnOverheat"` + TempSensors []temperatureSensor `json:"tempSensors"` + SystemStatus string `json:"systemStatus"` + } + + err := s.arista.structuredCall(&response, "show environment temperature") + if err != nil { + glog.Errorf("EOS Capi: show environment temperature: %v", err) + return nil, status.Error(codes.Unavailable, "EOS Capi call failed") + } + + if len(response) != 1 { + glog.Errorf("Expected 1-length result, got %d", len(response)) + return nil, status.Error(codes.Internal, "Internal error") + } + + d := response[0] + glog.Infof("%+v", d) + + return &pb.ShowEnvironmentTemperatureResponse{}, nil +}