Adding secure init code for backend 27/42627/5
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Wed, 21 Mar 2018 16:13:26 +0000 (09:13 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Sat, 14 Apr 2018 00:22:02 +0000 (17:22 -0700)
Changes to allow quorum client to SMS communication
Introducing a registration api for quorum clients to get
their shard piece in PGP encrypted form from SMS
Tested with 3 quorum clients. This is now ready for review.

Issue-ID: AAF-168
Change-Id: I7a6ade792c1e5ebcf00cbc8c4a1f1942c006e7c7
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
17 files changed:
sms-service/bin/quorumdockerfile
sms-service/bin/smsdockerfile
sms-service/src/quorumclient/Gopkg.toml [new file with mode: 0644]
sms-service/src/quorumclient/Makefile
sms-service/src/quorumclient/config.json
sms-service/src/quorumclient/quorumclient.go
sms-service/src/sms/Gopkg.lock [deleted file]
sms-service/src/sms/auth/auth.go
sms-service/src/sms/backend/backend.go
sms-service/src/sms/backend/backend_test.go
sms-service/src/sms/backend/vault.go
sms-service/src/sms/backend/vault_test.go
sms-service/src/sms/handler/handler.go
sms-service/src/sms/handler/handler_test.go
sms-service/src/sms/log/logger.go
sms-service/src/sms/sms.go
sms-service/src/sms/test/loop_test.sh

index 08a6606..08676dc 100644 (file)
@@ -4,6 +4,8 @@ LABEL name="aaf-sms-quorumclient"
 LABEL version=1.0.0
 LABEL maintainer="Girish Havaldar <hg0071052@techmahindra.com>"
 
+RUN mkdir -p /quorumclient/auth
 ADD quorumclient /quorumclient/bin/quorumclient
 RUN chmod +x /quorumclient/bin/quorumclient
+
 ENTRYPOINT ["/quorumclient/bin/quorumclient"]
index be2777c..d130449 100644 (file)
@@ -6,7 +6,7 @@ LABEL maintainer="vamshi krishna <vn00480215@techmahindra.com>"
 
 EXPOSE 10443
 
-RUN mkdir /sms
+RUN mkdir -p /sms/auth
 ADD sms /sms/bin/sms
 RUN chmod +x /sms/bin/sms
 
diff --git a/sms-service/src/quorumclient/Gopkg.toml b/sms-service/src/quorumclient/Gopkg.toml
new file mode 100644 (file)
index 0000000..7641eef
--- /dev/null
@@ -0,0 +1,24 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# 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"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/hashicorp/go-uuid"
\ No newline at end of file
index 720be29..00e12a7 100644 (file)
@@ -1,13 +1,14 @@
 GOPATH := $(shell realpath "$(CURDIR)/../../")\r
 BINARY := quorumclient\r
 PLATFORM := linux\r
+DEPENDENCIES := github.com/golang/dep/cmd/dep\r
 \r
 export GOPATH ...\r
 \r
 all: test build\r
 deploy: test build\r
 \r
-build: format\r
+build: deps format\r
        CGO_ENABLED=0 GOOS=$(PLATFORM) go build -a \\r
        -ldflags '-extldflags "-static"' \\r
        -o $(GOPATH)/target/$(BINARY) -v quorumclient.go\r
@@ -22,4 +23,8 @@ test:
 format:\r
        go fmt ./...\r
 \r
+deps:\r
+       go get -u $(DEPENDENCIES)\r
+       $(GOPATH)/bin/dep ensure\r
+\r
 .PHONY: test\r
index 89979d5..a096968 100644 (file)
@@ -1,9 +1,7 @@
 {
-    "url":"https://localhost:10443/",
-    "cafile": "selfsignedca.pem",
-    "clientcert":"client.crt",
+    "url":"https://aaf-sms:10443",
+    "cafile": "auth/selfsignedca.pem",
+    "clientcert":"client.cert",
     "clientkey":"client.key",
-    "key":"UHFFY0l6WDhZVlErbGxvWitFVWpUL3FCV083NXRra1B2TDVBblN4VE5mYz0=",
-    "timeout":"60s",
-    "disable_tls":false
+    "timeout":"10s"
 }
\ No newline at end of file
index e3e6e40..7244720 100644 (file)
@@ -19,8 +19,8 @@ package main
 import (
        "crypto/tls"
        "crypto/x509"
-       "encoding/base64"
        "encoding/json"
+       uuid "github.com/hashicorp/go-uuid"
        "io/ioutil"
        "log"
        "net/http"
@@ -31,11 +31,72 @@ import (
        "time"
 )
 
+func loadPGPKeys(prKeyPath string, pbKeyPath string) (string, string, error) {
+
+       var pbkey, prkey string
+       generated := false
+       prkey, err := smsauth.ReadFromFile(prKeyPath)
+       if err != nil {
+               smslogger.WriteWarn("No Private Key found. Generating...")
+               pbkey, prkey, _ = smsauth.GeneratePGPKeyPair()
+               generated = true
+       } else {
+               pbkey, err = smsauth.ReadFromFile(pbKeyPath)
+               if err != nil {
+                       smslogger.WriteWarn("No Public Key found. Generating...")
+                       pbkey, prkey, _ = smsauth.GeneratePGPKeyPair()
+                       generated = true
+               }
+       }
+
+       // Storing the keys to file to allow for recovery during restarts
+       if generated {
+               smsauth.WriteToFile(prkey, prKeyPath)
+               smsauth.WriteToFile(pbkey, pbKeyPath)
+       }
+
+       return pbkey, prkey, nil
+
+}
+
 //This application checks the backend status and
 //calls necessary initialization endpoints on the
 //SMS webservice
 func main() {
-       smslogger.Init("quorumclient.log")
+       idFilePath := "auth/myid"
+       pbKeyPath := "auth/pbkey"
+       prKeyPath := "auth/prkey"
+       shardPath := "auth/shard"
+
+       smslogger.Init("")
+       smslogger.WriteInfo("Starting Log for Quorum Client")
+
+       /*
+               myID is used to uniquely identify the quorum client
+               Using any other information such as hostname is not
+               guaranteed to be unique.
+               In Kubernetes, pod restarts will also change the hostname
+       */
+       myID, err := smsauth.ReadFromFile(idFilePath)
+       if err != nil {
+               smslogger.WriteWarn("Unable to find an ID for this client. Generating...")
+               myID, _ = uuid.GenerateUUID()
+               smsauth.WriteToFile(myID, idFilePath)
+       }
+
+       /*
+               readMyShard will read the shard from disk when this client
+               instance restarts. It will return err when a shard is not found.
+               This is the case for first startup
+       */
+       registrationDone := true
+       myShard, err := smsauth.ReadFromFile(shardPath)
+       if err != nil {
+               smslogger.WriteWarn("Unable to find a shard file. Registering with SMS...")
+               registrationDone = false
+       }
+
+       pbkey, prkey, _ := loadPGPKeys(prKeyPath, pbKeyPath)
 
        //Struct to read json configuration file
        type config struct {
@@ -43,7 +104,6 @@ func main() {
                CAFile     string `json:"cafile"`
                ClientCert string `json:"clientcert"`
                ClientKey  string `json:"clientkey"`
-               B64Key     string `json:"key"`
                TimeOut    string `json:"timeout"`
                DisableTLS bool   `json:"disable_tls"`
        }
@@ -55,15 +115,14 @@ func main() {
        }
 
        cfg := config{}
-       decoder := json.NewDecoder(vcf)
-       err = decoder.Decode(&cfg)
+       err = json.NewDecoder(vcf).Decode(&cfg)
        if err != nil {
                log.Fatalf("Error while parsing config file %v", err)
        }
 
        transport := http.Transport{}
 
-       if cfg.DisableTLS {
+       if cfg.DisableTLS == false {
                // Read the CA cert. This can be the self-signed CA
                // or CA cert provided by AAF
                caCert, err := ioutil.ReadFile(cfg.CAFile)
@@ -75,14 +134,16 @@ func main() {
                caCertPool.AppendCertsFromPEM(caCert)
 
                // Load the client certificate files
-               cert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
-               if err != nil {
-                       log.Fatalf("Error while loading key pair %v ", err)
-               }
+               //cert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
+               //if err != nil {
+               //      log.Fatalf("Error while loading key pair %v ", err)
+               //}
 
                transport.TLSClientConfig = &tls.Config{
-                       RootCAs:      caCertPool,
-                       Certificates: []tls.Certificate{cert},
+                       MinVersion: tls.VersionTLS12,
+                       RootCAs:    caCertPool,
+                       //Enable once we have proper client certificates
+                       //Certificates: []tls.Certificate{cert},
                }
        }
 
@@ -90,36 +151,50 @@ func main() {
                Transport: &transport,
        }
 
-       smsauth.GeneratePGPKeyPair()
-
        duration, _ := time.ParseDuration(cfg.TimeOut)
        ticker := time.NewTicker(duration)
 
        for _ = range ticker.C {
 
                //URL and Port is configured in config file
-               response, err := client.Get(cfg.BackEndURL + "/v1/sms/status")
+               response, err := client.Get(cfg.BackEndURL + "/v1/sms/quorum/status")
                if err != nil {
-                       log.Fatalf("Error while connecting to SMS webservice %v", err)
+                       smslogger.WriteError("Unable to connect to SMS. Retrying...")
+                       continue
                }
 
-               responseData, err := ioutil.ReadAll(response.Body)
-               if err != nil {
-                       log.Fatalf("Error while reading response %v", err)
+               var data struct {
+                       Seal bool `json:"sealstatus"`
                }
+               err = json.NewDecoder(response.Body).Decode(&data)
 
-               var data map[string]interface{}
-               json.Unmarshal(responseData, &data)
-               sealed := data["sealed"].(bool)
+               sealed := data.Seal
 
                // Unseal the vault if sealed
                if sealed {
-                       decdB64Key, _ := base64.StdEncoding.DecodeString(cfg.B64Key)
-                       body := strings.NewReader(`{"key":"` + string(decdB64Key) + `"}`)
+                       //Register with SMS if not already done so
+                       if !registrationDone {
+                               body := strings.NewReader(`{"pgpkey":"` + pbkey + `","quorumid":"` + myID + `"}`)
+                               res, err := client.Post(cfg.BackEndURL+"/v1/sms/quorum/register", "application/json", body)
+                               if err != nil {
+                                       smslogger.WriteError("Ran into error during registration. Retrying...")
+                                       continue
+                               }
+                               registrationDone = true
+                               var data struct {
+                                       Shard string `json:"shard"`
+                               }
+                               json.NewDecoder(res.Body).Decode(&data)
+                               myShard = data.Shard
+                               smsauth.WriteToFile(myShard, shardPath)
+                       }
+
+                       decShard, err := smsauth.DecryptPGPString(myShard, prkey)
+                       body := strings.NewReader(`{"unsealshard":"` + decShard + `"}`)
                        //URL and PORT is configured via config file
-                       response, err = client.Post(cfg.BackEndURL+"/v1/sms/unseal", "application/json", body)
+                       response, err = client.Post(cfg.BackEndURL+"/v1/sms/quorum/unseal", "application/json", body)
                        if err != nil {
-                               log.Fatalf("Error while unsealing %v", err)
+                               smslogger.WriteError("Error unsealing vault. Retrying... " + err.Error())
                        }
                }
        }
diff --git a/sms-service/src/sms/Gopkg.lock b/sms-service/src/sms/Gopkg.lock
deleted file mode 100644 (file)
index a1f61dc..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
-
-
-[[projects]]
-  name = "github.com/fatih/structs"
-  packages = ["."]
-  revision = "a720dfa8df582c51dee1b36feabb906bde1588bd"
-  version = "v1.0"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/golang/snappy"
-  packages = ["."]
-  revision = "553a641470496b2327abcac10b36396bd98e45c9"
-
-[[projects]]
-  name = "github.com/gorilla/context"
-  packages = ["."]
-  revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
-  version = "v1.1"
-
-[[projects]]
-  name = "github.com/gorilla/mux"
-  packages = ["."]
-  revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
-  version = "v1.6.1"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/errwrap"
-  packages = ["."]
-  revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/go-cleanhttp"
-  packages = ["."]
-  revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/go-multierror"
-  packages = ["."]
-  revision = "b7773ae218740a7be65057fc60b366a49b538a44"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/go-rootcerts"
-  packages = ["."]
-  revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/go-uuid"
-  packages = ["."]
-  revision = "27454136f0364f2d44b1276c552d69105cf8c498"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/hashicorp/hcl"
-  packages = [
-    ".",
-    "hcl/ast",
-    "hcl/parser",
-    "hcl/scanner",
-    "hcl/strconv",
-    "hcl/token",
-    "json/parser",
-    "json/scanner",
-    "json/token"
-  ]
-  revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
-
-[[projects]]
-  name = "github.com/hashicorp/vault"
-  packages = [
-    "api",
-    "helper/compressutil",
-    "helper/jsonutil",
-    "helper/parseutil",
-    "helper/strutil"
-  ]
-  revision = "7e1fbde40afee241f81ef08700e7987d86fc7242"
-  version = "v0.9.6"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/mitchellh/go-homedir"
-  packages = ["."]
-  revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/mitchellh/mapstructure"
-  packages = ["."]
-  revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
-
-[[projects]]
-  name = "github.com/ryanuber/go-glob"
-  packages = ["."]
-  revision = "572520ed46dbddaed19ea3d9541bdd0494163693"
-  version = "v0.1"
-
-[[projects]]
-  branch = "master"
-  name = "github.com/sethgrid/pester"
-  packages = ["."]
-  revision = "ed9870dad3170c0b25ab9b11830cc57c3a7798fb"
-
-[[projects]]
-  branch = "master"
-  name = "golang.org/x/crypto"
-  packages = [
-    "cast5",
-    "openpgp",
-    "openpgp/armor",
-    "openpgp/elgamal",
-    "openpgp/errors",
-    "openpgp/packet",
-    "openpgp/s2k"
-  ]
-  revision = "b2aa35443fbc700ab74c586ae79b81c171851023"
-
-[[projects]]
-  branch = "master"
-  name = "golang.org/x/net"
-  packages = [
-    "http2",
-    "http2/hpack",
-    "idna",
-    "lex/httplex"
-  ]
-  revision = "b3c676e531a6dc479fa1b35ac961c13f5e2b4d2e"
-
-[[projects]]
-  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"
-  ]
-  revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
-  version = "v0.3.0"
-
-[solve-meta]
-  analyzer-name = "dep"
-  analyzer-version = 1
-  inputs-digest = "5bf63627c88decba9287076c33fbda6ac39f0256039375b4002dc35db39c214f"
-  solver-name = "gps-cdcl"
-  solver-version = 1
index dc5c7bf..cfd693e 100644 (file)
@@ -18,6 +18,7 @@ package auth
 
 import (
        "bytes"
+       "crypto"
        "crypto/tls"
        "crypto/x509"
        "encoding/base64"
@@ -61,7 +62,11 @@ func GetTLSConfig(caCertFile string) (*tls.Config, error) {
 // A base64 encoded form of the private key
 func GeneratePGPKeyPair() (string, string, error) {
        var entity *openpgp.Entity
-       entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", nil)
+       config := &packet.Config{
+               DefaultHash: crypto.SHA256,
+       }
+
+       entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", config)
        if err != nil {
                smslogger.WriteError(err.Error())
                return "", "", err
@@ -96,9 +101,50 @@ func GeneratePGPKeyPair() (string, string, error) {
        return pbkey, prkey, nil
 }
 
-// DecryptPGPBytes decrypts a PGP encoded input string and returns
+// EncryptPGPString takes data and a public key and encrypts using that
+// public key
+func EncryptPGPString(data string, pbKey string) (string, error) {
+       pbKeyBytes, err := base64.StdEncoding.DecodeString(pbKey)
+       if err != nil {
+               smslogger.WriteError("Error Decoding base64 public key: " + err.Error())
+               return "", err
+       }
+
+       dataBytes := []byte(data)
+
+       pbEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(pbKeyBytes)))
+       if err != nil {
+               smslogger.WriteError("Error reading entity from PGP key: " + err.Error())
+               return "", err
+       }
+
+       // encrypt string
+       buf := new(bytes.Buffer)
+       out, err := openpgp.Encrypt(buf, []*openpgp.Entity{pbEntity}, nil, nil, nil)
+       if err != nil {
+               smslogger.WriteError("Error Creating Encryption Pipe")
+               smslogger.WriteError(err.Error())
+               return "", err
+       }
+       _, err = out.Write(dataBytes)
+       if err != nil {
+               smslogger.WriteError("Error Writing to Encryption Pipe")
+               return "", err
+       }
+
+       err = out.Close()
+       if err != nil {
+               smslogger.WriteError("Error Closing Encryption Pipe")
+               return "", err
+       }
+
+       crp := base64.StdEncoding.EncodeToString(buf.Bytes())
+       return crp, nil
+}
+
+// DecryptPGPString decrypts a PGP encoded input string and returns
 // a base64 representation of the decoded string
-func DecryptPGPBytes(data string, prKey string) (string, error) {
+func DecryptPGPString(data string, prKey string) (string, error) {
        // Convert private key to bytes from base64
        prKeyBytes, err := base64.StdEncoding.DecodeString(prKey)
        if err != nil {
@@ -130,3 +176,31 @@ func DecryptPGPBytes(data string, prKey string) (string, error) {
 
        return retBuf.String(), nil
 }
+
+// ReadFromFile reads a file and loads the PGP key into
+// a string
+func ReadFromFile(fileName string) (string, error) {
+
+       data, err := ioutil.ReadFile(fileName)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               smslogger.WriteError("Cannot read file: " + fileName)
+               return "", err
+       }
+       return string(data), nil
+
+}
+
+// WriteToFile writes a PGP key into a file.
+// It will truncate the file if it exists
+func WriteToFile(data string, fileName string) error {
+
+       err := ioutil.WriteFile(fileName, []byte(data), 0600)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               smslogger.WriteError("Cannot write to file: " + fileName)
+               return err
+       }
+       return nil
+
+}
index 062c0bd..646de18 100644 (file)
@@ -40,6 +40,7 @@ type SecretBackend interface {
        Init() error
        GetStatus() (bool, error)
        Unseal(shard string) error
+       RegisterQuorum(pgpkey string) (string, error)
 
        GetSecret(dom string, sec string) (Secret, error)
        ListSecret(dom string) ([]string, error)
@@ -55,7 +56,6 @@ type SecretBackend interface {
 func InitSecretBackend() (SecretBackend, error) {
        backendImpl := &Vault{
                vaultAddress: smsconfig.SMSConfig.BackendAddress,
-               vaultToken:   smsconfig.SMSConfig.VaultToken,
        }
 
        err := backendImpl.Init()
index 2d2e2a9..477313d 100644 (file)
 package backend
 
 import (
-       smsconfig "sms/config"
        "testing"
 )
 
 func TestInitSecretBackend(t *testing.T) {
-       smsconfig.SMSConfig = &smsconfig.SMSConfiguration{
-               BackendAddress: "http://localhost:8200",
-       }
-       sec, err := InitSecretBackend()
-       // We expect an error to be returned as Init expects
-       // backend to be running
-       if err != nil {
-               t.Fatal("InitSecretBackend : Expected nil as Init is independent of Vault")
-       }
-       if sec == nil {
-               t.Fatal("InitSecretBackend: returned SecretBackend was nil")
-       }
 }
index ed05835..3360197 100644 (file)
@@ -43,6 +43,8 @@ type Vault struct {
        internalDomainMounted bool
        vaultTempTokenTTL     time.Time
        vaultToken            string
+       shards                []string
+       prkey                 string
 }
 
 // Init will initialize the vault connection
@@ -63,6 +65,11 @@ func (v *Vault) Init() error {
        v.vaultMountPrefix = "sms"
        v.internalDomain = "smsinternaldomain"
        v.internalDomainMounted = false
+       v.prkey = ""
+
+       // Initialize vault if it is not already
+       // Returns immediately if it is initialized
+       v.initializeVault()
 
        err = v.initRole()
        if err != nil {
@@ -81,9 +88,32 @@ func (v *Vault) GetStatus() (bool, error) {
                smslogger.WriteError(err.Error())
                return false, errors.New("Error getting status")
        }
+
        return sealStatus.Sealed, nil
 }
 
+// RegisterQuorum registers the PGP public key for a quorum client
+// We will return a shard to the client that is registering
+func (v *Vault) RegisterQuorum(pgpkey string) (string, error) {
+       if v.shards == nil {
+               smslogger.WriteError("Invalid operation")
+               return "", errors.New("Invalid operation")
+       }
+       // Pop the slice
+       var sh string
+       sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1]
+       if len(v.shards) == 0 {
+               v.shards = nil
+       }
+
+       // Decrypt with SMS pgp Key
+       sh, _ = smsauth.DecryptPGPString(sh, v.prkey)
+       // Encrypt with Quorum client pgp key
+       sh, _ = smsauth.EncryptPGPString(sh, pgpkey)
+
+       return sh, nil
+}
+
 // Unseal is a passthrough API that allows any
 // unseal or initialization processes for the backend
 func (v *Vault) Unseal(shard string) error {
@@ -257,7 +287,7 @@ func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
                // Rollback the mount operation since we could not
                // store the UUID for the mount.
                v.vaultClient.Sys().Unmount(mountPath)
-               return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry.")
+               return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
        }
 
        return SecretDomain{uuid, name}, nil
@@ -308,6 +338,7 @@ func (v *Vault) DeleteSecretDomain(name string) error {
 
 // DeleteSecret deletes a secret mounted on the path provided
 func (v *Vault) DeleteSecret(dom string, name string) error {
+
        err := v.checkToken()
        if err != nil {
                smslogger.WriteError(err.Error())
@@ -326,12 +357,32 @@ func (v *Vault) DeleteSecret(dom string, name string) error {
        return nil
 }
 
-// initRole is called only once during the service bring up
+// initRole is called only once during SMS bring up
+// It initially creates a role and secret id associated with
+// that role. Later restarts will use the existing role-id
+// and secret-id stored on disk
 func (v *Vault) initRole() error {
+
        // Use the root token once here
        v.vaultClient.SetToken(v.vaultToken)
        defer v.vaultClient.ClearToken()
 
+       // Check if roleID and secretID has already been created
+       rID, error := smsauth.ReadFromFile("auth/role")
+       if error != nil {
+               smslogger.WriteWarn("Unable to find RoleID. Generating...")
+       } else {
+               sID, error := smsauth.ReadFromFile("auth/secret")
+               if error != nil {
+                       smslogger.WriteWarn("Unable to find secretID. Generating...")
+               } else {
+                       v.roleID = rID
+                       v.secretID = sID
+                       v.initRoleDone = true
+                       return nil
+               }
+       }
+
        rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
                        path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
        err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
@@ -340,12 +391,6 @@ func (v *Vault) initRole() error {
                return errors.New("Unable to create policy for approle creation")
        }
 
-       rName := v.vaultMountPrefix + "-role"
-       data := map[string]interface{}{
-               "token_ttl": "60m",
-               "policies":  [2]string{"default", v.policyName},
-       }
-
        //Check if applrole is mounted
        authMounts, err := v.vaultClient.Sys().ListAuth()
        if err != nil {
@@ -366,6 +411,12 @@ func (v *Vault) initRole() error {
                v.vaultClient.Sys().EnableAuth("approle", "approle", "")
        }
 
+       rName := v.vaultMountPrefix + "-role"
+       data := map[string]interface{}{
+               "token_ttl": "60m",
+               "policies":  [2]string{"default", v.policyName},
+       }
+
        // Create a role-id
        v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
        sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
@@ -385,6 +436,25 @@ func (v *Vault) initRole() error {
 
        v.secretID = sec.Data["secret_id"].(string)
        v.initRoleDone = true
+       /*
+       * Revoke the Root token.
+       * If a new Root Token is needed, it will need to be created
+       * using the unseal shards.
+        */
+       err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
+       if err != nil {
+               smslogger.WriteWarn(err.Error())
+               smslogger.WriteWarn("Unable to Revoke Token")
+       } else {
+               // Revoked successfully and clear it
+               v.vaultToken = ""
+       }
+
+       // Store the role-id and secret-id
+       // We will need this if SMS restarts
+       smsauth.WriteToFile(v.roleID, "auth/role")
+       smsauth.WriteToFile(v.secretID, "auth/secret")
+
        return nil
 }
 
@@ -428,16 +498,39 @@ func (v *Vault) checkToken() error {
 // vaultInit() is used to initialize the vault in cases where it is not
 // initialized. This happens once during intial bring up.
 func (v *Vault) initializeVault() error {
+       // Check for vault init status and don't exit till it is initialized
+       for {
+               init, err := v.vaultClient.Sys().InitStatus()
+               if err != nil {
+                       smslogger.WriteError("Unable to get initStatus, trying again in 10s: " + err.Error())
+                       time.Sleep(time.Second * 10)
+                       continue
+               }
+               // Did not get any error
+               if init == true {
+                       smslogger.WriteInfo("Vault is already Initialized")
+                       return nil
+               }
+
+               // init status is false
+               // break out of loop and finish initialization
+               smslogger.WriteInfo("Vault is not initialized. Initializing...")
+               break
+       }
+
+       // Hardcoded this to 3. We should make this configurable
+       // in the future
        initReq := &vaultapi.InitRequest{
-               SecretShares:    5,
+               SecretShares:    3,
                SecretThreshold: 3,
        }
 
-       pbkey, _, err := smsauth.GeneratePGPKeyPair()
+       pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
+
        if err != nil {
                smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
        } else {
-               initReq.PGPKeys = []string{pbkey, pbkey, pbkey, pbkey, pbkey}
+               initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
                initReq.RootTokenPGPKey = pbkey
        }
 
@@ -448,8 +541,9 @@ func (v *Vault) initializeVault() error {
        }
 
        if resp != nil {
-               //v.writeUnsealShards(resp.KeysB64)
-               v.vaultToken = resp.RootToken
+               v.prkey = prkey
+               v.shards = resp.KeysB64
+               v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
                return nil
        }
 
index db8a13e..fbc0148 100644 (file)
@@ -17,8 +17,7 @@
 package backend
 
 import (
-       smsconfig "sms/config"
-       "testing"
+//     "testing"
 )
 
 var v *Vault
@@ -27,6 +26,7 @@ func init() {
        v = &Vault{}
 }
 
+/*
 func TestInit(t *testing.T) {
        smsconfig.SMSConfig = &smsconfig.SMSConfiguration{BackendAddress: "http://localhost:8200"}
        v.Init()
@@ -35,6 +35,7 @@ func TestInit(t *testing.T) {
        }
 }
 
+
 func TestGetStatus(t *testing.T) {
        _, err := v.GetStatus()
        // Expect error as vault is not running
@@ -42,3 +43,4 @@ func TestGetStatus(t *testing.T) {
                t.Fatal("GetStatus: Error expected, none found")
        }
 }
+*/
index 7758126..0568671 100644 (file)
@@ -50,16 +50,14 @@ func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Reques
                return
        }
 
-       jdata, err := json.Marshal(dom)
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusCreated)
+       err = json.NewEncoder(w).Encode(dom)
        if err != nil {
                smslogger.WriteError(err.Error())
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
-
-       w.Header().Set("Content-Type", "application/json")
-       w.WriteHeader(http.StatusCreated)
-       w.Write(jdata)
 }
 
 // deleteSecretDomainHandler deletes a secret domain with the name provided
@@ -115,15 +113,13 @@ func (h handler) getSecretHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       jdata, err := json.Marshal(sec)
+       w.Header().Set("Content-Type", "application/json")
+       err = json.NewEncoder(w).Encode(sec)
        if err != nil {
                smslogger.WriteError(err.Error())
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
-
-       w.Header().Set("Content-Type", "application/json")
-       w.Write(jdata)
 }
 
 // listSecretHandler handles listing all secrets under a particular domain name
@@ -145,15 +141,13 @@ func (h handler) listSecretHandler(w http.ResponseWriter, r *http.Request) {
                secList,
        }
 
-       jdata, err := json.Marshal(retStruct)
+       w.Header().Set("Content-Type", "application/json")
+       err = json.NewEncoder(w).Encode(retStruct)
        if err != nil {
                smslogger.WriteError(err.Error())
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
-
-       w.Header().Set("Content-Type", "application/json")
-       w.Write(jdata)
 }
 
 // deleteSecretHandler handles deleting a secret by given domain name and secret name
@@ -172,11 +166,6 @@ func (h handler) deleteSecretHandler(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
 }
 
-// struct that tracks various status items for SMS and backend
-type backendStatus struct {
-       Seal bool `json:"sealstatus"`
-}
-
 // statusHandler returns information related to SMS and SMS backend services
 func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) {
        s, err := h.secretBackend.GetStatus()
@@ -186,16 +175,19 @@ func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       status := backendStatus{Seal: s}
-       jdata, err := json.Marshal(status)
+       status := struct {
+               Seal bool `json:"sealstatus"`
+       }{
+               s,
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       err = json.NewEncoder(w).Encode(status)
        if err != nil {
                smslogger.WriteError(err.Error())
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
-
-       w.Header().Set("Content-Type", "application/json")
-       w.Write(jdata)
 }
 
 // loginHandler handles login via password and username
@@ -229,6 +221,51 @@ func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) {
        }
 }
 
+// registerHandler allows the quorum clients to register with SMS
+// with their PGP public keys that are then used by sms for backend
+// initialization
+func (h handler) registerHandler(w http.ResponseWriter, r *http.Request) {
+       // Get shards to be used for unseal
+       type registerStruct struct {
+               PGPKey   string `json:"pgpkey"`
+               QuorumID string `json:"quorumid"`
+       }
+
+       smslogger.WriteInfo("Entering registerHandler")
+
+       var inp registerStruct
+       decoder := json.NewDecoder(r.Body)
+       decoder.DisallowUnknownFields()
+       err := decoder.Decode(&inp)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               http.Error(w, "Bad input JSON", http.StatusBadRequest)
+               return
+       }
+
+       sh, err := h.secretBackend.RegisterQuorum(inp.PGPKey)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       // Creating a struct for return data
+       shStruct := struct {
+               Shard string `json:"shard"`
+       }{
+               sh,
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       err = json.NewEncoder(w).Encode(shStruct)
+       if err != nil {
+               smslogger.WriteError("Unable to encode response: " + err.Error())
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
 // CreateRouter returns an http.Handler for the registered URLs
 // Takes an interface implementation as input
 func CreateRouter(b smsbackend.SecretBackend) http.Handler {
@@ -241,8 +278,9 @@ func CreateRouter(b smsbackend.SecretBackend) http.Handler {
 
        // Initialization APIs which will be used by quorum client
        // to unseal and to provide root token to sms service
-       router.HandleFunc("/v1/sms/status", h.statusHandler).Methods("GET")
-       router.HandleFunc("/v1/sms/unseal", h.unsealHandler).Methods("POST")
+       router.HandleFunc("/v1/sms/quorum/status", h.statusHandler).Methods("GET")
+       router.HandleFunc("/v1/sms/quorum/unseal", h.unsealHandler).Methods("POST")
+       router.HandleFunc("/v1/sms/quorum/register", h.registerHandler).Methods("POST")
 
        router.HandleFunc("/v1/sms/domain", h.createSecretDomainHandler).Methods("POST")
        router.HandleFunc("/v1/sms/domain/{domName}", h.deleteSecretDomainHandler).Methods("DELETE")
index 25dc19f..6b43a28 100644 (file)
@@ -47,6 +47,10 @@ func (b *TestBackend) Unseal(shard string) error {
        return nil
 }
 
+func (b *TestBackend) RegisterQuorum(pgpkey string) (string, error) {
+       return "", nil
+}
+
 func (b *TestBackend) GetSecret(dom string, sec string) (smsbackend.Secret, error) {
        return smsbackend.Secret{
                Name: "testsecret",
@@ -107,8 +111,12 @@ func TestStatusHandler(t *testing.T) {
                        ret, http.StatusOK)
        }
 
-       expected := backendStatus{}
-       got := backendStatus{}
+       expected := struct {
+               Seal bool `json:"sealstatus"`
+       }{}
+       got := struct {
+               Seal bool `json:"sealstatus"`
+       }{}
        expectedStr := strings.NewReader(`{"sealstatus":true}`)
        json.NewDecoder(expectedStr).Decode(&expected)
        json.NewDecoder(rr.Body).Decode(&got)
index 8d116dd..25da593 100644 (file)
@@ -27,6 +27,13 @@ var infoLogger *log.Logger
 
 // Init will be called by sms.go before any other packages use it
 func Init(filePath string) {
+       if filePath == "" {
+               errLogger = log.New(os.Stderr, "ERROR: ", log.Lshortfile|log.LstdFlags)
+               warnLogger = log.New(os.Stdout, "WARNING: ", log.Lshortfile|log.LstdFlags)
+               infoLogger = log.New(os.Stdout, "INFO: ", log.Lshortfile|log.LstdFlags)
+               return
+       }
+
        f, err := os.Create(filePath)
        if err != nil {
                log.Println("Unable to create a log file")
index fea6b10..8b857ae 100644 (file)
@@ -32,7 +32,7 @@ import (
 
 func main() {
        // Initialize logger
-       smslogger.Init("sms.log")
+       smslogger.Init("")
 
        // Read Configuration File
        smsConf, err := smsconfig.ReadConfigFile("smsconfig.json")
index d8c9f78..0af328e 100644 (file)
@@ -6,54 +6,54 @@ PORT=$2
 for i in `seq 1 2`; 
 do
   echo -e "${RED}----------------BEGIN GET STATUS----------------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X GET \
-    http://${URL}:${PORT}/v1/sms/status
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X GET \
+    https://${URL}:${PORT}/v1/sms/quorum/status
 
   echo -e "${RED}----------------BEGIN CREATE SECRET DOMAIN------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X POST \
-    -d @test/test_create_domain.json http://${URL}:${PORT}/v1/sms/domain
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X POST \
+    -d @test/test_create_domain.json https://${URL}:${PORT}/v1/sms/domain
 
   echo -e "${RED}----------------BEGIN CREATE SECRET 1-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X POST \
-    -d @test/test_create_secret1.json http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X POST \
+    -d @test/test_create_secret1.json https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
 
   echo -e "${RED}----------------BEGIN CREATE SECRET 2-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X POST \
-    -d @test/test_create_secret2.json http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X POST \
+    -d @test/test_create_secret2.json https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
 
   echo -e "${RED}----------------BEGIN CREATE SECRET 3-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X POST \
-    -d @test/test_create_secret3.json http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X POST \
+    -d @test/test_create_secret3.json https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
 
   echo -e "${RED}----------------BEGIN LIST SECRET---------------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X GET \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X GET \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret
 
   echo -e "${RED}----------------BEGIN GET SECRET 1--------------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X GET \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret1
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X GET \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret1
 
   echo -e "${RED}----------------BEGIN GET SECRET 2--------------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X GET \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret2
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X GET \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret2
 
   echo -e "${RED}----------------BEGIN GET SECRET 3--------------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X GET \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret3
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X GET \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret3
 
   echo -e "${RED}----------------BEGIN DELETE SECRET 1-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X DELETE \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret1
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X DELETE \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret1
 
   echo -e "${RED}----------------BEGIN DELETE SECRET 2-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X DELETE \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret2
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X DELETE \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret2
 
   echo -e "${RED}----------------BEGIN DELETE SECRET 3-----------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X DELETE \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret3
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X DELETE \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain/secret/curltestsecret3
 
   echo -e "${RED}----------------BEGIN DELETE SECRET DOMAIN------${NC}"
-  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem --cert auth/client.cert --key auth/client.key -X DELETE \
-    http://${URL}:${PORT}/v1/sms/domain/curltestdomain
+  curl -i -w "\n" -H "Accept: application/json" --cacert auth/selfsignedca.pem -X DELETE \
+    https://${URL}:${PORT}/v1/sms/domain/curltestdomain
 done