Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
SDI-2730: Add SSL/TLC capability to snap-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
candysmurf committed Aug 3, 2017
1 parent af85fe2 commit e76a351
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 27 deletions.
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,94 @@ $ snaptel -p plugin list
$ snaptel -p metric list
$ snaptel -p plugin load /opt/snap/plugins/snap-plugin-collector-mock1
$ snaptel -p task create -t mock-file.yml
```
```
### Secure GRPC plugins
Snap supports TLS for GRPC plugins. Referring to [secure plugin communication](https://github.com/intelsdi-x/snap/blob/master/docs/SECURE_PLUGIN_COMMUNICATION.md) for details. How to setup TLS on both server and client? The [Setup TLS Certificates](https://github.com/intelsdi-x/snap/blob/master/docs/SETUP_TLS_CERTIFICATES.md) has everything.
#### Sample Use Cases
| Flag | Description |
| ------ | ------ |
| tls-cert | TLS client certificate |
| tls-key | TLS client private key |
| ca-cert-paths | TLS client CA certificates |
| plugin-cert | TLS server certificate |
| plugin-key | TLS server private key |
| plugin-ca-certs | TLS server CA certificates |
##### Case 1: Start `snapteld` with TLS certs
Snap is a client for all GRPC plugins. Note that Snap loads CA certificates from your OS certificate trust store if it's not specified.
```sh
$snapteld -t 0 -l 1 --tls-cert snaptest-cli.crt --tls-key snaptest-cli.key --ca-cert-paths snaptest-ca.crt
```
##### Case 1: Run `snaptel`
```sh
▶ snaptel plugin load --plugin-cert snaptest-srv.crt --plugin-key snaptest-srv.key --plugin-ca-certs snaptest-ca.crt ../snap-plugin-lib-go/rand-collector
Error: Both plugin certification and key are mandatory. The request has to use HTTPS
Usage: load <plugin_path> [--plugin-cert=<plugin_cert_path> --plugin-key=<plugin_key_path> --plugin-ca-certs=<ca_cert_paths>]
```
> :collision: Urgh! Loading a secured GRPC plugin has to use HTTPS
```sh
▶ snaptel --url https://localhost:8181 plugin load --plugin-cert snaptest-srv.crt --plugin-key snaptest-srv.key --plugin-ca-certs snaptest-ca.crt ../snap-plugin-lib-go/rand-collector
Error: Error: Post https://localhost:8181/v2/plugins: http: server gave HTTP response to HTTPS client
Usage: load <plugin_path> [--plugin-cert=<plugin_cert_path> --plugin-key=<plugin_key_path> --plugin-ca-certs=<ca_cert_paths>]
```
> :collision: The server was not started using HTTPs
##### Case 2: Start `snapteld` with TLS certs and HTTPS
Snap only requires the server certificate verificate for HTTPS.
```sh
▶ snapteld -t 0 -l 1 --rest-https --rest-cert snaphttps-srv.crt --rest-key snaphttps-srv.key --tls-cert snaptest-cli.crt --tls-key snaptest-cli.key --ca-cert-paths snaptest-ca.crt
```
> :white_check_mark: using this setting to start `snapteld` for a seured GRPC plugin communication.
##### Case 2: Run `snaptel`
```sh
▶ snaptel --url https://localhost:8181 plugin load --plugin-cert snaptest-srv.crt --plugin-key snaptest-srv.key --plugin-ca-certs snaptest-ca.crt ../snap-plugin-lib-go/rand-collector
Error: Error: Post https://localhost:8181/v2/plugins: x509: certificate signed by unknown authority
Usage: load <plugin_path> [--plugin-cert=<plugin_cert_path> --plugin-key=<plugin_key_path> --plugin-ca-certs=<ca_cert_paths>]
```
> :collision: Urgh! HTTPS does not have a trusted CA. There is no way to specify a CA using a flag for HTTPS currently. Putting the trusted CA in your OS trust store in production. Using --insecure flag for your testing convenience.
```sh
▶ snaptel --url https://localhost:8181 --insecure plugin load --plugin-cert snaptest-srv.crt --plugin-key snaptest-srv.key --plugin-ca-certs snaptest-ca.crt ../snap-plugin-lib-go/rand-collector
Plugin loaded
Name: test-rand-collector
Version: 1
Type: collector
Signed: false
Loaded Time: Wed, 02 Aug 2017 15:23:09 PDT
```
>:white_check_mark: The secured GRPC plugin loaded! You may omit the `plugin-ca-certs` flag if it's in the trust store of your OS/App.
Only loading a GRPC plugin requires TLS certs. Not any other commands.
```sh
▶ snaptel --url https://localhost:8181 --insecure plugin list
NAME VERSION TYPE SIGNED STATUS LOADED TIME
test-rand-collector 1 collector false loaded Wed, 02 Aug 2017 15:23:09 PDT
```
##### Case 3: Caveat
Starting `snapteld` same as case 2. Loading a non GRPC plugin.
```sh
▶ snaptel --url https://localhost:8181 --insecure plugin load --plugin-cert snaptest-srv.crt --plugin-key snaptest-srv.key --plugin-ca-certs snaptest-ca.crt ../snap/build/darwin/x86_64/plugins/snap-plugin-collector-mock1
Error: secure framework can't connect to insecure plugin; plugin_name: mock
Usage: load <plugin_path> [--plugin-cert=<plugin_cert_path> --plugin-key=<plugin_key_path> --plugin-ca-certs=<ca_cert_paths>]
```
>:collision: Urgh! Currently, no TLS is available for non-grpc plugins. Restarting `snapteld` without TLS to load non-grpc plugins.
21 changes: 0 additions & 21 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,8 @@ import:
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- package: github.com/ghodss/yaml
version: c3eb24aeea63668ebdac08d2e252f20df8b6b1ae
- package: github.com/golang/protobuf
version: 888eb0692c857ec880338addf316bd662d5e630e
subpackages:
- proto
- package: github.com/hashicorp/go-msgpack
version: fa3f63826f7c23912c15263591e65d54d080b458
subpackages:
- codec
- package: github.com/hashicorp/memberlist
version: a93fbd426dd831f5a66db3adc6a5ffa6f44cc60a
- package: github.com/intelsdi-x/gomit
- package: github.com/julienschmidt/httprouter
version: 8c199fb6259ffc1af525cc3ad52ee60ba8359669
- package: github.com/pborman/uuid
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
- package: github.com/robfig/cron
version: 32d9c273155a0506d27cf73dd1246e86a470997e
- package: github.com/vrischmann/jsonutil
version: 694784f9315ee9fc763c1d30f28753cba21307aa
- package: github.com/xeipuuv/gojsonschema
version: d3178baac32433047aa76f07317f84fbe2be6cda
- package: golang.org/x/crypto
version: aedad9a179ec1ea11b7064c57cbc6dc30d7724ec
subpackages:
Expand All @@ -47,8 +28,6 @@ import:
- context
- trace
- http2
- package: google.golang.org/grpc
version: 0032a855ba5c8a3c8e0d71c2deef354b70af1584
- package: gopkg.in/yaml.v2
version: c1cd2254a6dd314c9d73c338c12688c9325d85c6
- package: github.com/intelsdi-x/snap-client-go/client
26 changes: 25 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ limitations under the License.
package main

import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"os"
"sort"

openapiclient "github.com/go-openapi/runtime/client"
"github.com/golang/glog"
"github.com/intelsdi-x/snap-cli/snaptel"
"github.com/intelsdi-x/snap-client-go/client"
Expand All @@ -35,6 +38,10 @@ var (
gitversion string
)

type tlsClientOptions struct {
insecureSkipVerify bool
}

func main() {
app := cli.NewApp()
app.Name = "snaptel"
Expand Down Expand Up @@ -66,13 +73,30 @@ func beforeAction(ctx *cli.Context) error {
glog.Fatal(err)
}

c := client.NewHTTPClientWithConfig(nil, &client.TransportConfig{Host: u.Host, BasePath: snaptel.FlAPIVer.Value, Schemes: []string{u.Scheme}})
tlcOpts := tlsClientOptions{insecureSkipVerify: ctx.Bool("insecure")}
tlcClient := tlsClient(tlcOpts)
rt := openapiclient.NewWithClient(u.Host, snaptel.FlAPIVer.Value, []string{u.Scheme}, tlcClient)
c := client.New(rt, nil)
snaptel.SetClient(c)
snaptel.SetScheme(u.Scheme)
snaptel.SetAuthInfo(snaptel.BasicAuth(ctx))

return nil
}

// tlsClient creates a http.Client
func tlsClient(opts tlsClientOptions) *http.Client {
transport := tlsTransport(opts)
return &http.Client{Transport: transport}
}

func tlsTransport(opts tlsClientOptions) http.RoundTripper {
cfg := &tls.Config{}
cfg.InsecureSkipVerify = opts.insecureSkipVerify
cfg.BuildNameToCertificate()
return &http.Transport{TLSClientConfig: cfg}
}

// ByCommand contains array of CLI commands.
type ByCommand []cli.Command

Expand Down
29 changes: 28 additions & 1 deletion snaptel/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"

"golang.org/x/crypto/ssh/terminal"

Expand All @@ -39,6 +40,7 @@ var (
client *snapClient.Snap
authInfoWriter runtime.ClientAuthInfoWriter
password string
scheme string
)

// UsageError defines the error message and CLI context
Expand All @@ -61,7 +63,7 @@ func newUsageError(s string, ctx *cli.Context) UsageError {
return UsageError{s, ctx}
}

// SetClient provides a way to set the private snapClient in this package.
// SetClient sets the private HTTP Client in this package.
func SetClient(cl *snapClient.Snap) {
client = cl
}
Expand All @@ -71,6 +73,11 @@ func SetAuthInfo(aw runtime.ClientAuthInfoWriter) {
authInfoWriter = aw
}

// SetScheme sets the request protocol.
func SetScheme(s string) {
scheme = s
}

// GetFirstChar gets the first character of a giving string.
func GetFirstChar(s string) string {
firstChar := ""
Expand Down Expand Up @@ -142,6 +149,10 @@ func getErrorDetail(err error, ctx *cli.Context) error {
case *tasks.UpdateTaskStateUnauthorized:
return newUsageError(fmt.Sprintf("%v", err.(*tasks.UpdateTaskStateUnauthorized).Payload.Message), ctx)
default:
// this is a hack
if strings.Contains(err.Error(), "tls: oversized record") || strings.Contains(err.Error(), "malformed HTTP response") {
return newUsageError(extractError(err.Error()), ctx)
}
return newUsageError(fmt.Sprintf("Error: %v", err), ctx)
}
}
Expand Down Expand Up @@ -210,3 +221,19 @@ func BasicAuth(ctx *cli.Context) runtime.ClientAuthInfoWriter {
}
return nil
}

// extractError is a hack for SSL/TLS handshake error.
func extractError(m string) string {
ts := strings.Split(m, "\"")

var tss []string
if len(ts) > 0 {
tss = strings.Split(ts[0], "malformed")
}

errMsg := "Error connecting to API. Do you have an http/https mismatching API request?"
if len(tss) > 0 {
errMsg = tss[0] + errMsg
}
return errMsg
}
51 changes: 50 additions & 1 deletion snaptel/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,49 @@ func loadPlugin(ctx *cli.Context) error {
}

params := plugins.NewLoadPluginParamsWithTimeout(FlTimeout.Value)
f, err := os.Open(filepath.Join(paths...))

// Sets the plugin data.
f, err := os.Open(filepath.Join(paths[0]))
if err != nil {
return newUsageError("Cannot open the plugin", ctx)
}
defer f.Close()
params.SetPluginData(f)

if !hasValidFlags(ctx.IsSet("plugin-cert"), ctx.IsSet("plugin-key"), scheme) {
return newUsageError("Both plugin certification and key are mandatory. The request has to use HTTPS", ctx)
}

// Sets the plugin certificate.
if ctx.IsSet("plugin-cert") {
cert, err := os.Open(ctx.String("plugin-cert"))
if err != nil {
return newUsageError("Cannot open the plugin certificate", ctx)
}
defer cert.Close()
params.SetPluginCert(cert)
}

// Sets the plugin key.
if ctx.IsSet("plugin-key") {
key, err := os.Open(ctx.String("plugin-key"))
if err != nil {
return newUsageError("Cannot open the plugin key", ctx)
}
defer key.Close()
params.SetPluginKey(key)
}

// Sets the CA ceritificate.
if ctx.IsSet("plugin-ca-certs") {
caCert, err := os.Open(ctx.String("plugin-ca-certs"))
if err != nil {
return newUsageError("Cannot open the CA certificate", ctx)
}
defer caCert.Close()
params.SetCaCerts(caCert)
}

resp, err := client.Plugins.LoadPlugin(params, authInfoWriter)
if err != nil {
return getErrorDetail(err, ctx)
Expand Down Expand Up @@ -154,3 +190,16 @@ func listPlugins(ctx *cli.Context) error {

return nil
}

func hasValidFlags(key, cert bool, scheme string) bool {
// Validats TLS plugin load flags
if key && cert && scheme == "https" {
return true
}

// Don't block normal flow
if !key && !cert {
return true
}
return false
}
12 changes: 10 additions & 2 deletions snaptel/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package snaptel
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -54,8 +55,15 @@ func watchTask(ctx *cli.Context) error {
// Therefore no timeout for this request.
req, err := http.NewRequest("GET", url, nil)
req.SetBasicAuth("snap", password)
cli := &http.Client{}
resp, err := cli.Do(req)
if err != nil {
return err
}

tr := http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
wtClient := http.Client{Transport: &tr}
resp, err := wtClient.Do(req)
if err != nil {
return err
}
Expand Down

0 comments on commit e76a351

Please sign in to comment.