From f93a27fbcbf1260a92ff2eeed91dd083bace65c1 Mon Sep 17 00:00:00 2001 From: Naresh Kumar Thota <30426686+nareshkumarthota@users.noreply.github.com> Date: Fri, 22 Sep 2017 20:18:07 +0000 Subject: [PATCH] Mutual TLS support added for Activity (#21) --- .../secure-rest-conditional-gateway.json | 19 ++- ext/flogo/activity/rest/README.md | 16 ++ ext/flogo/activity/rest/activity.go | 81 +++++++++- ext/flogo/activity/rest/activity.json | 12 ++ lib/flow/SecureRestInvoker.json | 144 ++++++++++++++++++ 5 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 lib/flow/SecureRestInvoker.json diff --git a/cli/samples/secure-rest-conditional-gateway/secure-rest-conditional-gateway.json b/cli/samples/secure-rest-conditional-gateway/secure-rest-conditional-gateway.json index ffe0b912..78eb805d 100644 --- a/cli/samples/secure-rest-conditional-gateway/secure-rest-conditional-gateway.json +++ b/cli/samples/secure-rest-conditional-gateway/secure-rest-conditional-gateway.json @@ -1,7 +1,7 @@ { "mashling_schema": "0.2", "gateway": { - "name": "clientAuthGw", + "name": "secureGwApp", "version": "1.0.0", "display_name":"Secure Rest Conditional Gateway", "display_image":"displayImage.svg", @@ -22,8 +22,8 @@ "settings": { "port": "9097", "enableTLS": "true", - "serverCert":"${ENV.SERVER_CERT}", - "serverKey":"${ENV.SERVER_KEY}" + "serverCert":"gateway.crt", + "serverKey":"gateway.key" } }, { @@ -33,10 +33,10 @@ "settings": { "port": "9098", "enableTLS": "true", - "serverCert":"/Users/rpolishe/sslcerts/domain.crt", - "serverKey":"/Users/rpolishe/sslcerts/domain.key", + "serverCert":"gateway.crt", + "serverKey":"gateway.key", "enableClientAuth": "true", - "trustStore": "${ENV.TRUST_STORE}" + "trustStore": "truststore" } } ], @@ -106,6 +106,11 @@ "name": "animals_get_handler", "description": "Handle other animals", "reference": "github.com/TIBCOSoftware/mashling/lib/flow/RestTriggerToRestGetActivity.json" + }, + { + "name": "animals_get_handler_mutual_ssl", + "description": "Handle other animals", + "reference": "github.com/TIBCOSoftware/mashling/lib/flow/SecureRestInvoker.json" } ], "event_links": [ @@ -145,7 +150,7 @@ "triggers": ["get_animals_rest_secure_client_auth_trigger"], "dispatches": [ { - "handler": "animals_get_handler" + "handler": "animals_get_handler_mutual_ssl" } ] } diff --git a/ext/flogo/activity/rest/README.md b/ext/flogo/activity/rest/README.md index 3bd8d61f..93343d78 100755 --- a/ext/flogo/activity/rest/README.md +++ b/ext/flogo/activity/rest/README.md @@ -43,6 +43,18 @@ Inputs and Outputs: { "name": "tracing", "type": "any" + }, + { + "name": "serverCert", + "type": "string" + }, + { + "name": "serverKey", + "type": "string" + }, + { + "name": "trustStore", + "type": "string" } ], "outputs": [ @@ -63,6 +75,10 @@ Inputs and Outputs: | content | The message content | | params | The path parameters (Deprecated) | | tracing | The tracing context to forward | +| serverCert | The server certificate file path | +| serverKey | The server key file path | +| trustStore | Folder path containing trusted certificates | + Note: * **pathParams**: Is only required if you have params in your URI ( i.e. http://.../pet/:id ) diff --git a/ext/flogo/activity/rest/activity.go b/ext/flogo/activity/rest/activity.go index 682ce190..4d8ae701 100755 --- a/ext/flogo/activity/rest/activity.go +++ b/ext/flogo/activity/rest/activity.go @@ -2,11 +2,15 @@ package rest import ( "bytes" + "crypto/tls" + "crypto/x509" "encoding/json" + "fmt" "io" "io/ioutil" "net/http" "net/url" + "os" "strings" "github.com/TIBCOSoftware/flogo-lib/core/activity" @@ -32,6 +36,9 @@ const ( ivContent = "content" ivParams = "params" ivTracing = "tracing" + ivServerCert = "serverCert" + ivServerKey = "serverKey" + ivTrustStore = "trustStore" ovResult = "result" ovTracing = "tracing" @@ -144,7 +151,47 @@ func (a *RESTActivity) Eval(context activity.Context) (done bool, err error) { opentracing.HTTPHeadersCarrier(req.Header)) } - client := &http.Client{} + //Enable transport layaer security + log.SetLogLevel(logger.DebugLevel) + + serverCert, _ := context.GetInput(ivServerCert).(string) + serverKey, _ := context.GetInput(ivServerKey).(string) + trustStore, _ := context.GetInput(ivTrustStore).(string) + + tlsConfig := &tls.Config{} + + if serverCert != "" && serverKey != "" { + //gateway certificates are available + //load gateway certificate-key pair + log.Debug("Loading gateway certificate - key pair...") + cert, err := tls.LoadX509KeyPair(serverCert, serverKey) + if err != nil { + log.Errorf("Unable to load cert - %v", err) + } else { + tlsConfig.Certificates = []tls.Certificate{cert} + log.Debug("Loading gateway certificate - key pair DONE") + } + } + + if trustStore != "" { + //trust store directory is available + //load trusted certificates present inside the dir + log.Debug("Loading truststore...") + trustRootCAPool, err := getCerts(trustStore) + if err != nil { + log.Errorf("Error while loading trust store - %v", err) + } else { + tlsConfig.RootCAs = trustRootCAPool + log.Debug("Loading truststore DONE") + } + } + + tlsConfig.BuildNameToCertificate() + + client := &http.Client{ + Transport: &http.Transport{TLSClientConfig: tlsConfig}, + } + resp, err := client.Do(req) if err != nil { if span != nil { @@ -263,3 +310,35 @@ func BuildURI(uri string, values map[string]string) string { return buffer.String() } + +func getCerts(trustStore string) (*x509.CertPool, error) { + certPool := x509.NewCertPool() + fileInfo, err := os.Stat(trustStore) + if err != nil { + return certPool, fmt.Errorf("Truststore [%s] does not exist", trustStore) + } + switch mode := fileInfo.Mode(); { + case mode.IsDir(): + break + case mode.IsRegular(): + return certPool, fmt.Errorf("Truststore [%s] is not a directory. Must be a directory containing trusted certificates in PEM format", + trustStore) + } + trustedCertFiles, err := ioutil.ReadDir(trustStore) + if err != nil || len(trustedCertFiles) == 0 { + return certPool, fmt.Errorf("Failed to read trusted certificates from [%s] Must be a directory containing trusted certificates in PEM format", trustStore) + } + for _, trustCertFile := range trustedCertFiles { + fqfName := fmt.Sprintf("%s%c%s", trustStore, os.PathSeparator, trustCertFile.Name()) + trustCertBytes, err := ioutil.ReadFile(fqfName) + if err != nil { + log.Warnf("Failed to read trusted certificate [%s] ... continueing", trustCertFile.Name()) + } + log.Debugf("Loading cert file - %v", fqfName) + certPool.AppendCertsFromPEM(trustCertBytes) + } + if len(certPool.Subjects()) < 1 { + return certPool, fmt.Errorf("Failed to read trusted certificates from [%s] After processing all files in the directory no valid trusted certs were found", trustStore) + } + return certPool, nil +} diff --git a/ext/flogo/activity/rest/activity.json b/ext/flogo/activity/rest/activity.json index b97b60d9..cad91e07 100755 --- a/ext/flogo/activity/rest/activity.json +++ b/ext/flogo/activity/rest/activity.json @@ -33,6 +33,18 @@ { "name": "tracing", "type": "any" + }, + { + "name": "serverCert", + "type": "string" + }, + { + "name": "serverKey", + "type": "string" + }, + { + "name": "trustStore", + "type": "string" } ], "outputs": [ diff --git a/lib/flow/SecureRestInvoker.json b/lib/flow/SecureRestInvoker.json new file mode 100644 index 00000000..a0111ebc --- /dev/null +++ b/lib/flow/SecureRestInvoker.json @@ -0,0 +1,144 @@ +{ + "name": "newapp", + "type": "flogo:app", + "version": "0.0.1", + "description": "", + "triggers": [ + { + "name": "gorillamuxtrigger", + "ref": "github.com/TIBCOSoftware/mashling/ext/flogo/trigger/gorillamuxtrigger", + "description": "gorillamuxtrigger REST Trigger", + "settings": { + "port": "9096" + }, + "id": "receive_http_message", + "handlers": [ + { + "settings": { + "method": "GET", + "path": "/users/:id", + "autoIdReply": "false", + "useReplyHandler": "false" + }, + "actionId": "check" + } + ] + } + ], + "actions": [ + { + "name": "check", + "data": { + "flow": { + "type": 1, + "explicitReply": true, + "attributes": [], + "rootTask": { + "id": 1, + "type": 1, + "tasks": [ + { + "id": 2, + "name": "Invoke REST Service", + "description": "Simple REST Activity", + "type": 1, + "activityType": "github-com-tibco-software-flogo-contrib-activity-rest", + "activityRef": "github.com/TIBCOSoftware/mashling/ext/flogo/activity/rest", + "attributes": [ + { + "name": "method", + "value": "GET", + "required": true, + "type": "string" + }, + { + "name": "uri", + "value": "https://localhost:8080/", + "required": true, + "type": "string" + }, + { + "name": "pathParams", + "value": null, + "required": false, + "type": "params" + }, + { + "name": "queryParams", + "value": null, + "required": false, + "type": "params" + }, + { + "name": "content", + "value": null, + "required": false, + "type": "any" + }, + { + "name": "serverCert", + "value": "gateway.crt", + "required": false, + "type": "string" + }, + { + "name": "serverKey", + "value": "gateway.key", + "required": false, + "type": "string" + }, + { + "name": "trustStore", + "value": "truststore", + "required": false, + "type": "string" + } + ] + }, + { + "id": 3, + "name": "Reply To Trigger", + "description": "Simple Reply Activity", + "type": 1, + "activityType": "github-com-tibco-software-flogo-contrib-activity-reply", + "activityRef": "github.com/TIBCOSoftware/flogo-contrib/activity/reply", + "attributes": [ + { + "name": "code", + "value": "200", + "required": true, + "type": "integer" + }, + { + "name": "data", + "value": "success", + "required": false, + "type": "any" + } + ], + "inputMappings": [ + { + "type": 1, + "value": "{A2.result}", + "mapTo": "data" + } + ] + } + ], + "links": [ + { + "id": 1, + "from": 2, + "to": 3, + "type": 0 + } + ], + "attributes": [] + } + } + }, + "id": "check", + "ref": "github.com/TIBCOSoftware/flogo-contrib/action/flow" + } + ] +} \ No newline at end of file