summaryrefslogtreecommitdiffstats
path: root/sms-service
diff options
context:
space:
mode:
authorKiran Kamineni <kiran.k.kamineni@intel.com>2018-03-21 09:13:26 -0700
committerKiran Kamineni <kiran.k.kamineni@intel.com>2018-04-13 17:22:02 -0700
commitce74c5ac55fdb51582268046632943e510a19f00 (patch)
treea0beb2382abe73f4db19c58aaa625f818a010800 /sms-service
parent2dd9f3de5b33d6acbcb641566b9e7d3ccbe91d8c (diff)
Adding secure init code for backend
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>
Diffstat (limited to 'sms-service')
-rw-r--r--sms-service/bin/quorumdockerfile2
-rw-r--r--sms-service/bin/smsdockerfile2
-rw-r--r--sms-service/src/quorumclient/Gopkg.toml24
-rw-r--r--sms-service/src/quorumclient/Makefile7
-rw-r--r--sms-service/src/quorumclient/config.json10
-rw-r--r--sms-service/src/quorumclient/quorumclient.go127
-rw-r--r--sms-service/src/sms/Gopkg.lock161
-rw-r--r--sms-service/src/sms/auth/auth.go80
-rw-r--r--sms-service/src/sms/backend/backend.go2
-rw-r--r--sms-service/src/sms/backend/backend_test.go13
-rw-r--r--sms-service/src/sms/backend/vault.go120
-rw-r--r--sms-service/src/sms/backend/vault_test.go6
-rw-r--r--sms-service/src/sms/handler/handler.go88
-rw-r--r--sms-service/src/sms/handler/handler_test.go12
-rw-r--r--sms-service/src/sms/log/logger.go7
-rw-r--r--sms-service/src/sms/sms.go2
-rw-r--r--sms-service/src/sms/test/loop_test.sh52
17 files changed, 434 insertions, 281 deletions
diff --git a/sms-service/bin/quorumdockerfile b/sms-service/bin/quorumdockerfile
index 08a6606..08676dc 100644
--- a/sms-service/bin/quorumdockerfile
+++ b/sms-service/bin/quorumdockerfile
@@ -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"]
diff --git a/sms-service/bin/smsdockerfile b/sms-service/bin/smsdockerfile
index be2777c..d130449 100644
--- a/sms-service/bin/smsdockerfile
+++ b/sms-service/bin/smsdockerfile
@@ -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
index 0000000..7641eef
--- /dev/null
+++ b/sms-service/src/quorumclient/Gopkg.toml
@@ -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
diff --git a/sms-service/src/quorumclient/Makefile b/sms-service/src/quorumclient/Makefile
index 720be29..00e12a7 100644
--- a/sms-service/src/quorumclient/Makefile
+++ b/sms-service/src/quorumclient/Makefile
@@ -1,13 +1,14 @@
GOPATH := $(shell realpath "$(CURDIR)/../../")
BINARY := quorumclient
PLATFORM := linux
+DEPENDENCIES := github.com/golang/dep/cmd/dep
export GOPATH ...
all: test build
deploy: test build
-build: format
+build: deps format
CGO_ENABLED=0 GOOS=$(PLATFORM) go build -a \
-ldflags '-extldflags "-static"' \
-o $(GOPATH)/target/$(BINARY) -v quorumclient.go
@@ -22,4 +23,8 @@ test:
format:
go fmt ./...
+deps:
+ go get -u $(DEPENDENCIES)
+ $(GOPATH)/bin/dep ensure
+
.PHONY: test
diff --git a/sms-service/src/quorumclient/config.json b/sms-service/src/quorumclient/config.json
index 89979d5..a096968 100644
--- a/sms-service/src/quorumclient/config.json
+++ b/sms-service/src/quorumclient/config.json
@@ -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
diff --git a/sms-service/src/quorumclient/quorumclient.go b/sms-service/src/quorumclient/quorumclient.go
index e3e6e40..7244720 100644
--- a/sms-service/src/quorumclient/quorumclient.go
+++ b/sms-service/src/quorumclient/quorumclient.go
@@ -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
index a1f61dc..0000000
--- a/sms-service/src/sms/Gopkg.lock
+++ /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
diff --git a/sms-service/src/sms/auth/auth.go b/sms-service/src/sms/auth/auth.go
index dc5c7bf..cfd693e 100644
--- a/sms-service/src/sms/auth/auth.go
+++ b/sms-service/src/sms/auth/auth.go
@@ -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
+
+}
diff --git a/sms-service/src/sms/backend/backend.go b/sms-service/src/sms/backend/backend.go
index 062c0bd..646de18 100644
--- a/sms-service/src/sms/backend/backend.go
+++ b/sms-service/src/sms/backend/backend.go
@@ -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()
diff --git a/sms-service/src/sms/backend/backend_test.go b/sms-service/src/sms/backend/backend_test.go
index 2d2e2a9..477313d 100644
--- a/sms-service/src/sms/backend/backend_test.go
+++ b/sms-service/src/sms/backend/backend_test.go
@@ -17,21 +17,8 @@
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")
- }
}
diff --git a/sms-service/src/sms/backend/vault.go b/sms-service/src/sms/backend/vault.go
index ed05835..3360197 100644
--- a/sms-service/src/sms/backend/vault.go
+++ b/sms-service/src/sms/backend/vault.go
@@ -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
}
diff --git a/sms-service/src/sms/backend/vault_test.go b/sms-service/src/sms/backend/vault_test.go
index db8a13e..fbc0148 100644
--- a/sms-service/src/sms/backend/vault_test.go
+++ b/sms-service/src/sms/backend/vault_test.go
@@ -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")
}
}
+*/
diff --git a/sms-service/src/sms/handler/handler.go b/sms-service/src/sms/handler/handler.go
index 7758126..0568671 100644
--- a/sms-service/src/sms/handler/handler.go
+++ b/sms-service/src/sms/handler/handler.go
@@ -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")
diff --git a/sms-service/src/sms/handler/handler_test.go b/sms-service/src/sms/handler/handler_test.go
index 25dc19f..6b43a28 100644
--- a/sms-service/src/sms/handler/handler_test.go
+++ b/sms-service/src/sms/handler/handler_test.go
@@ -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)
diff --git a/sms-service/src/sms/log/logger.go b/sms-service/src/sms/log/logger.go
index 8d116dd..25da593 100644
--- a/sms-service/src/sms/log/logger.go
+++ b/sms-service/src/sms/log/logger.go
@@ -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")
diff --git a/sms-service/src/sms/sms.go b/sms-service/src/sms/sms.go
index fea6b10..8b857ae 100644
--- a/sms-service/src/sms/sms.go
+++ b/sms-service/src/sms/sms.go
@@ -32,7 +32,7 @@ import (
func main() {
// Initialize logger
- smslogger.Init("sms.log")
+ smslogger.Init("")
// Read Configuration File
smsConf, err := smsconfig.ReadConfigFile("smsconfig.json")
diff --git a/sms-service/src/sms/test/loop_test.sh b/sms-service/src/sms/test/loop_test.sh
index d8c9f78..0af328e 100644
--- a/sms-service/src/sms/test/loop_test.sh
+++ b/sms-service/src/sms/test/loop_test.sh
@@ -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