Adding system test coverage support 03/38203/1
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Fri, 23 Mar 2018 21:14:10 +0000 (14:14 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Fri, 23 Mar 2018 21:17:47 +0000 (14:17 -0700)
Added support for running system level code coverage tasks
Updated sms.go to allow graceful shutdown when it
gets a SIGINT. Useful for gather coverage information.

Issue-ID: AAF-192
Change-Id: Ife4a485e7926fd59948bf90fac4b2d4ea9de0332
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
sms-service/doc/coverage.html [new file with mode: 0644]
sms-service/src/sms/Gopkg.lock
sms-service/src/sms/Gopkg.toml
sms-service/src/sms/coverage.md [new file with mode: 0644]
sms-service/src/sms/sms.go
sms-service/src/sms/sms_test.go [new file with mode: 0644]

diff --git a/sms-service/doc/coverage.html b/sms-service/doc/coverage.html
new file mode 100644 (file)
index 0000000..d03ddde
--- /dev/null
@@ -0,0 +1,1182 @@
+
+<!DOCTYPE html>
+<html>
+       <head>
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+               <style>
+                       body {
+                               background: black;
+                               color: rgb(80, 80, 80);
+                       }
+                       body, pre, #legend span {
+                               font-family: Menlo, monospace;
+                               font-weight: bold;
+                       }
+                       #topbar {
+                               background: black;
+                               position: fixed;
+                               top: 0; left: 0; right: 0;
+                               height: 42px;
+                               border-bottom: 1px solid rgb(80, 80, 80);
+                       }
+                       #content {
+                               margin-top: 50px;
+                       }
+                       #nav, #legend {
+                               float: left;
+                               margin-left: 10px;
+                       }
+                       #legend {
+                               margin-top: 12px;
+                       }
+                       #nav {
+                               margin-top: 10px;
+                       }
+                       #legend span {
+                               margin: 0 5px;
+                       }
+                       .cov0 { color: rgb(192, 0, 0) }
+.cov1 { color: rgb(128, 128, 128) }
+.cov2 { color: rgb(116, 140, 131) }
+.cov3 { color: rgb(104, 152, 134) }
+.cov4 { color: rgb(92, 164, 137) }
+.cov5 { color: rgb(80, 176, 140) }
+.cov6 { color: rgb(68, 188, 143) }
+.cov7 { color: rgb(56, 200, 146) }
+.cov8 { color: rgb(44, 212, 149) }
+.cov9 { color: rgb(32, 224, 152) }
+.cov10 { color: rgb(20, 236, 155) }
+
+               </style>
+       </head>
+       <body>
+               <div id="topbar">
+                       <div id="nav">
+                               <select id="files">
+                               
+                               <option value="file0">sms/auth/auth.go (17.6%)</option>
+                               
+                               <option value="file1">sms/backend/backend.go (66.7%)</option>
+                               
+                               <option value="file2">sms/backend/vault.go (60.5%)</option>
+                               
+                               <option value="file3">sms/config/config.go (90.9%)</option>
+                               
+                               <option value="file4">sms/handler/handler.go (55.1%)</option>
+                               
+                               <option value="file5">sms/log/logger.go (31.2%)</option>
+                               
+                               <option value="file6">sms/sms.go (82.6%)</option>
+                               
+                               </select>
+                       </div>
+                       <div id="legend">
+                               <span>not tracked</span>
+                       
+                               <span class="cov0">no coverage</span>
+                               <span class="cov1">low coverage</span>
+                               <span class="cov2">*</span>
+                               <span class="cov3">*</span>
+                               <span class="cov4">*</span>
+                               <span class="cov5">*</span>
+                               <span class="cov6">*</span>
+                               <span class="cov7">*</span>
+                               <span class="cov8">*</span>
+                               <span class="cov9">*</span>
+                               <span class="cov10">high coverage</span>
+                       
+                       </div>
+               </div>
+               <div id="content">
+               
+               <pre class="file" id="file0" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import (
+        "bytes"
+        "crypto/tls"
+        "crypto/x509"
+        "encoding/base64"
+        "golang.org/x/crypto/openpgp"
+        "golang.org/x/crypto/openpgp/packet"
+        "io/ioutil"
+
+        smslogger "sms/log"
+)
+
+var tlsConfig *tls.Config
+
+// GetTLSConfig initializes a tlsConfig using the CA's certificate
+// This config is then used to enable the server for mutual TLS
+func GetTLSConfig(caCertFile string) (*tls.Config, error) <span class="cov10" title="3">{
+        // Initialize tlsConfig once
+        if tlsConfig == nil </span><span class="cov10" title="3">{
+                caCert, err := ioutil.ReadFile(caCertFile)
+
+                if err != nil </span><span class="cov1" title="1">{
+                        return nil, err
+                }</span>
+
+                <span class="cov6" title="2">caCertPool := x509.NewCertPool()
+                caCertPool.AppendCertsFromPEM(caCert)
+
+                tlsConfig = &amp;tls.Config{
+                        ClientAuth: tls.RequireAndVerifyClientCert,
+                        ClientCAs:  caCertPool,
+                        MinVersion: tls.VersionTLS12,
+                }
+                tlsConfig.BuildNameToCertificate()</span>
+        }
+        <span class="cov6" title="2">return tlsConfig, nil</span>
+}
+
+// GeneratePGPKeyPair produces a PGP key pair and returns
+// two things:
+// A base64 encoded form of the public part of the entity
+// A base64 encoded form of the private key
+func GeneratePGPKeyPair() (string, string, error) <span class="cov0" title="0">{
+        var entity *openpgp.Entity
+        entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", nil)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return "", "", err
+        }</span>
+
+        // Sign the identity in the entity
+        <span class="cov0" title="0">for _, id := range entity.Identities </span><span class="cov0" title="0">{
+                err = id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil)
+                if err != nil </span><span class="cov0" title="0">{
+                        smslogger.WriteError(err.Error())
+                        return "", "", err
+                }</span>
+        }
+
+        // Sign the subkey in the entity
+        <span class="cov0" title="0">for _, subkey := range entity.Subkeys </span><span class="cov0" title="0">{
+                err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, nil)
+                if err != nil </span><span class="cov0" title="0">{
+                        smslogger.WriteError(err.Error())
+                        return "", "", err
+                }</span>
+        }
+
+        <span class="cov0" title="0">buffer := new(bytes.Buffer)
+        entity.Serialize(buffer)
+        pbkey := base64.StdEncoding.EncodeToString(buffer.Bytes())
+
+        buffer.Reset()
+        entity.SerializePrivate(buffer, nil)
+        prkey := base64.StdEncoding.EncodeToString(buffer.Bytes())
+
+        return pbkey, prkey, nil</span>
+}
+
+// DecryptPGPBytes decrypts a PGP encoded input string and returns
+// a base64 representation of the decoded string
+func DecryptPGPBytes(data string, prKey string) (string, error) <span class="cov0" title="0">{
+        // Convert private key to bytes from base64
+        prKeyBytes, err := base64.StdEncoding.DecodeString(prKey)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError("Error Decoding base64 private key: " + err.Error())
+                return "", err
+        }</span>
+
+        <span class="cov0" title="0">dataBytes, err := base64.StdEncoding.DecodeString(data)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError("Error Decoding base64 data: " + err.Error())
+                return "", err
+        }</span>
+
+        <span class="cov0" title="0">prEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(prKeyBytes)))
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError("Error reading entity from PGP key: " + err.Error())
+                return "", err
+        }</span>
+
+        <span class="cov0" title="0">prEntityList := &amp;openpgp.EntityList{prEntity}
+        message, err := openpgp.ReadMessage(bytes.NewBuffer(dataBytes), prEntityList, nil, nil)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError("Error Decrypting message: " + err.Error())
+                return "", err
+        }</span>
+
+        <span class="cov0" title="0">var retBuf bytes.Buffer
+        retBuf.ReadFrom(message.UnverifiedBody)
+
+        return retBuf.String(), nil</span>
+}
+</pre>
+               
+               <pre class="file" id="file1" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package backend
+
+import (
+        smsconfig "sms/config"
+        smslogger "sms/log"
+)
+
+// SecretDomain is where Secrets are stored.
+// A single domain can have any number of secrets
+type SecretDomain struct {
+        UUID string `json:"uuid"`
+        Name string `json:"name"`
+}
+
+// Secret is the struct that defines the structure of a secret
+// It consists of a name and map containing key value pairs
+type Secret struct {
+        Name   string                 `json:"name"`
+        Values map[string]interface{} `json:"values"`
+}
+
+// SecretBackend interface that will be implemented for various secret backends
+type SecretBackend interface {
+        Init() error
+        GetStatus() (bool, error)
+        Unseal(shard string) error
+
+        GetSecret(dom string, sec string) (Secret, error)
+        ListSecret(dom string) ([]string, error)
+
+        CreateSecretDomain(name string) (SecretDomain, error)
+        CreateSecret(dom string, sec Secret) error
+
+        DeleteSecretDomain(name string) error
+        DeleteSecret(dom string, name string) error
+}
+
+// InitSecretBackend returns an interface implementation
+func InitSecretBackend() (SecretBackend, error) <span class="cov10" title="2">{
+        backendImpl := &amp;Vault{
+                vaultAddress: smsconfig.SMSConfig.VaultAddress,
+                vaultToken:   smsconfig.SMSConfig.VaultToken,
+        }
+
+        err := backendImpl.Init()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return nil, err
+        }</span>
+
+        <span class="cov10" title="2">return backendImpl, nil</span>
+}
+
+// LoginBackend Interface that will be implemented for various login backends
+type LoginBackend interface {
+}
+</pre>
+               
+               <pre class="file" id="file2" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package backend
+
+import (
+        uuid "github.com/hashicorp/go-uuid"
+        vaultapi "github.com/hashicorp/vault/api"
+        smsauth "sms/auth"
+        smslogger "sms/log"
+
+        "errors"
+        "fmt"
+        "strings"
+        "sync"
+        "time"
+)
+
+// Vault is the main Struct used in Backend to initialize the struct
+type Vault struct {
+        sync.Mutex
+        engineType        string
+        initRoleDone      bool
+        policyName        string
+        roleID            string
+        secretID          string
+        vaultAddress      string
+        vaultClient       *vaultapi.Client
+        vaultMount        string
+        vaultTempTokenTTL time.Time
+        vaultToken        string
+        unsealShards      []string
+        rootToken         string
+        pgpPub            string
+        pgpPr             string
+}
+
+// Init will initialize the vault connection
+// It will also create the initial policy if it does not exist
+// TODO: Check to see if we need to wait for vault to be running
+func (v *Vault) Init() error <span class="cov4" title="3">{
+        vaultCFG := vaultapi.DefaultConfig()
+        vaultCFG.Address = v.vaultAddress
+        client, err := vaultapi.NewClient(vaultCFG)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create new vault client")
+        }</span>
+
+        <span class="cov4" title="3">v.engineType = "kv"
+        v.initRoleDone = false
+        v.policyName = "smsvaultpolicy"
+        v.vaultClient = client
+        v.vaultMount = "sms"
+
+        err = v.initRole()
+        if err != nil </span><span class="cov2" title="2">{
+                smslogger.WriteError(err.Error())
+                smslogger.WriteInfo("InitRole will try again later")
+        }</span>
+
+        <span class="cov4" title="3">return nil</span>
+}
+
+// GetStatus returns the current seal status of vault
+func (v *Vault) GetStatus() (bool, error) <span class="cov4" title="3">{
+        sys := v.vaultClient.Sys()
+        sealStatus, err := sys.SealStatus()
+        if err != nil </span><span class="cov1" title="1">{
+                smslogger.WriteError(err.Error())
+                return false, errors.New("Error getting status")
+        }</span>
+
+        <span class="cov2" title="2">return sealStatus.Sealed, nil</span>
+}
+
+// Unseal is a passthrough API that allows any
+// unseal or initialization processes for the backend
+func (v *Vault) Unseal(shard string) error <span class="cov0" title="0">{
+        sys := v.vaultClient.Sys()
+        _, err := sys.Unseal(shard)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to execute unseal operation with specified shard")
+        }</span>
+
+        <span class="cov0" title="0">return nil</span>
+}
+
+// GetSecret returns a secret mounted on a particular domain name
+// The secret itself is referenced via its name which translates to
+// a mount path in vault
+func (v *Vault) GetSecret(dom string, name string) (Secret, error) <span class="cov6" title="6">{
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return Secret{}, errors.New("Token check failed")
+        }</span>
+
+        <span class="cov6" title="6">dom = v.vaultMount + "/" + dom
+
+        sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return Secret{}, errors.New("Unable to read Secret at provided path")
+        }</span>
+
+        // sec and err are nil in the case where a path does not exist
+        <span class="cov6" title="6">if sec == nil </span><span class="cov0" title="0">{
+                smslogger.WriteWarn("Vault read was empty. Invalid Path")
+                return Secret{}, errors.New("Secret not found at the provided path")
+        }</span>
+
+        <span class="cov6" title="6">return Secret{Name: name, Values: sec.Data}, nil</span>
+}
+
+// ListSecret returns a list of secret names on a particular domain
+// The values of the secret are not returned
+func (v *Vault) ListSecret(dom string) ([]string, error) <span class="cov2" title="2">{
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return nil, errors.New("Token check failed")
+        }</span>
+
+        <span class="cov2" title="2">dom = v.vaultMount + "/" + dom
+
+        sec, err := v.vaultClient.Logical().List(dom)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return nil, errors.New("Unable to read Secret at provided path")
+        }</span>
+
+        // sec and err are nil in the case where a path does not exist
+        <span class="cov2" title="2">if sec == nil </span><span class="cov0" title="0">{
+                smslogger.WriteWarn("Vaultclient returned empty data")
+                return nil, errors.New("Secret not found at the provided path")
+        }</span>
+
+        <span class="cov2" title="2">val, ok := sec.Data["keys"].([]interface{})
+        if !ok </span><span class="cov0" title="0">{
+                smslogger.WriteError("Secret not found at the provided path")
+                return nil, errors.New("Secret not found at the provided path")
+        }</span>
+
+        <span class="cov2" title="2">retval := make([]string, len(val))
+        for i, v := range val </span><span class="cov6" title="6">{
+                retval[i] = fmt.Sprint(v)
+        }</span>
+
+        <span class="cov2" title="2">return retval, nil</span>
+}
+
+// CreateSecretDomain mounts the kv backend on a path with the given name
+func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) <span class="cov2" title="2">{
+        // Check if token is still valid
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return SecretDomain{}, errors.New("Token Check failed")
+        }</span>
+
+        <span class="cov2" title="2">name = strings.TrimSpace(name)
+        mountPath := v.vaultMount + "/" + name
+        mountInput := &amp;vaultapi.MountInput{
+                Type:        v.engineType,
+                Description: "Mount point for domain: " + name,
+                Local:       false,
+                SealWrap:    false,
+                Config:      vaultapi.MountConfigInput{},
+        }
+
+        err = v.vaultClient.Sys().Mount(mountPath, mountInput)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return SecretDomain{}, errors.New("Unable to create Secret Domain")
+        }</span>
+
+        <span class="cov2" title="2">uuid, _ := uuid.GenerateUUID()
+        return SecretDomain{uuid, name}, nil</span>
+}
+
+// CreateSecret creates a secret mounted on a particular domain name
+// The secret itself is mounted on a path specified by name
+func (v *Vault) CreateSecret(dom string, sec Secret) error <span class="cov6" title="6">{
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Token check failed")
+        }</span>
+
+        <span class="cov6" title="6">dom = v.vaultMount + "/" + dom
+
+        // Vault return is empty on successful write
+        // TODO: Check if values is not empty
+        _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create Secret at provided path")
+        }</span>
+
+        <span class="cov6" title="6">return nil</span>
+}
+
+// DeleteSecretDomain deletes a secret domain which translates to
+// an unmount operation on the given path in Vault
+func (v *Vault) DeleteSecretDomain(name string) error <span class="cov2" title="2">{
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Token Check Failed")
+        }</span>
+
+        <span class="cov2" title="2">name = strings.TrimSpace(name)
+        mountPath := v.vaultMount + "/" + name
+
+        err = v.vaultClient.Sys().Unmount(mountPath)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to delete domain specified")
+        }</span>
+
+        <span class="cov2" title="2">return nil</span>
+}
+
+// DeleteSecret deletes a secret mounted on the path provided
+func (v *Vault) DeleteSecret(dom string, name string) error <span class="cov6" title="6">{
+        err := v.checkToken()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Token check failed")
+        }</span>
+
+        <span class="cov6" title="6">dom = v.vaultMount + "/" + dom
+
+        // Vault return is empty on successful delete
+        _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to delete Secret at provided path")
+        }</span>
+
+        <span class="cov6" title="6">return nil</span>
+}
+
+// initRole is called only once during the service bring up
+func (v *Vault) initRole() error <span class="cov4" title="3">{
+        // Use the root token once here
+        v.vaultClient.SetToken(v.vaultToken)
+        defer v.vaultClient.ClearToken()
+
+        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)
+        if err != nil </span><span class="cov2" title="2">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create policy for approle creation")
+        }</span>
+
+        <span class="cov1" title="1">rName := v.vaultMount + "-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 </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to get mounted auth backends")
+        }</span>
+
+        <span class="cov1" title="1">approleMounted := false
+        for k, v := range authMounts </span><span class="cov1" title="1">{
+                if v.Type == "approle" &amp;&amp; k == "approle/" </span><span class="cov1" title="1">{
+                        approleMounted = true
+                        break</span>
+                }
+        }
+
+        // Mount approle in case its not already mounted
+        <span class="cov1" title="1">if !approleMounted </span><span class="cov0" title="0">{
+                v.vaultClient.Sys().EnableAuth("approle", "approle", "")
+        }</span>
+
+        // Create a role-id
+        <span class="cov1" title="1">v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
+        sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create role ID for approle")
+        }</span>
+        <span class="cov1" title="1">v.roleID = sec.Data["role_id"].(string)
+
+        // Create a secret-id to go with it
+        sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
+                map[string]interface{}{})
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create secret ID for role")
+        }</span>
+
+        <span class="cov1" title="1">v.secretID = sec.Data["secret_id"].(string)
+        v.initRoleDone = true
+        return nil</span>
+}
+
+// Function checkToken() gets called multiple times to create
+// temporary tokens
+func (v *Vault) checkToken() error <span class="cov10" title="24">{
+        v.Lock()
+        defer v.Unlock()
+
+        // Init Role if it is not yet done
+        // Role needs to be created before token can be created
+        if v.initRoleDone == false </span><span class="cov0" title="0">{
+                err := v.initRole()
+                if err != nil </span><span class="cov0" title="0">{
+                        smslogger.WriteError(err.Error())
+                        return errors.New("Unable to initRole in checkToken")
+                }</span>
+        }
+
+        // Return immediately if token still has life
+        <span class="cov10" title="24">if v.vaultClient.Token() != "" &amp;&amp;
+                time.Since(v.vaultTempTokenTTL) &lt; time.Minute*50 </span><span class="cov9" title="23">{
+                return nil
+        }</span>
+
+        // Create a temporary token using our roleID and secretID
+        <span class="cov1" title="1">out, err := v.vaultClient.Logical().Write("auth/approle/login",
+                map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("Unable to create Temporary Token for Role")
+        }</span>
+
+        <span class="cov1" title="1">tok, err := out.TokenID()
+
+        v.vaultTempTokenTTL = time.Now()
+        v.vaultClient.SetToken(tok)
+        return nil</span>
+}
+
+// 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 <span class="cov0" title="0">{
+        initReq := &amp;vaultapi.InitRequest{
+                SecretShares:    5,
+                SecretThreshold: 3,
+        }
+
+        pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
+        }</span><span class="cov0" title="0"> else {
+                initReq.PGPKeys = []string{pbkey, pbkey, pbkey, pbkey, pbkey}
+                initReq.RootTokenPGPKey = pbkey
+                v.pgpPub = pbkey
+                v.pgpPr = prkey
+        }</span>
+
+        <span class="cov0" title="0">resp, err := v.vaultClient.Sys().Init(initReq)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                return errors.New("FATAL: Unable to initialize Vault")
+        }</span>
+
+        <span class="cov0" title="0">if resp != nil </span><span class="cov0" title="0">{
+                v.unsealShards = resp.KeysB64
+                v.rootToken = resp.RootToken
+                return nil
+        }</span>
+
+        <span class="cov0" title="0">return errors.New("FATAL: Init response was empty")</span>
+}
+</pre>
+               
+               <pre class="file" id="file3" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+        "encoding/json"
+        "os"
+)
+
+// SMSConfiguration loads up all the values that are used to configure
+// backend implementations
+// TODO: Review these and see if they can be created/discovered dynamically
+type SMSConfiguration struct {
+        CAFile     string `json:"cafile"`
+        ServerCert string `json:"servercert"`
+        ServerKey  string `json:"serverkey"`
+
+        VaultAddress string `json:"vaultaddress"`
+        VaultToken   string `json:"vaulttoken"`
+}
+
+// SMSConfig is the structure that stores the configuration
+var SMSConfig *SMSConfiguration
+
+// ReadConfigFile reads the specified smsConfig file to setup some env variables
+func ReadConfigFile(file string) (*SMSConfiguration, error) <span class="cov10" title="3">{
+        if SMSConfig == nil </span><span class="cov10" title="3">{
+                f, err := os.Open(file)
+                if err != nil </span><span class="cov1" title="1">{
+                        return nil, err
+                }</span>
+                <span class="cov6" title="2">defer f.Close()
+
+                SMSConfig = &amp;SMSConfiguration{}
+                decoder := json.NewDecoder(f)
+                err = decoder.Decode(SMSConfig)
+                if err != nil </span><span class="cov0" title="0">{
+                        return nil, err
+                }</span>
+        }
+
+        <span class="cov6" title="2">return SMSConfig, nil</span>
+}
+</pre>
+               
+               <pre class="file" id="file4" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package handler
+
+import (
+        "encoding/json"
+        "github.com/gorilla/mux"
+        "net/http"
+
+        smsbackend "sms/backend"
+        smslogger "sms/log"
+)
+
+// handler stores two interface implementations that implement
+// the backend functionality
+type handler struct {
+        secretBackend smsbackend.SecretBackend
+        loginBackend  smsbackend.LoginBackend
+}
+
+// createSecretDomainHandler creates a secret domain with a name provided
+func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
+        var d smsbackend.SecretDomain
+
+        err := json.NewDecoder(r.Body).Decode(&amp;d)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusBadRequest)
+                return
+        }</span>
+
+        <span class="cov6" title="3">dom, err := h.secretBackend.CreateSecretDomain(d.Name)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">jdata, err := json.Marshal(dom)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">w.Header().Set("Content-Type", "application/json")
+        w.WriteHeader(http.StatusCreated)
+        w.Write(jdata)</span>
+}
+
+// deleteSecretDomainHandler deletes a secret domain with the name provided
+func (h handler) deleteSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
+        vars := mux.Vars(r)
+        domName := vars["domName"]
+
+        err := h.secretBackend.DeleteSecretDomain(domName)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">w.WriteHeader(http.StatusNoContent)</span>
+}
+
+// createSecretHandler handles creation of secrets on a given domain name
+func (h handler) createSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
+        // Get domain name from URL
+        vars := mux.Vars(r)
+        domName := vars["domName"]
+
+        // Get secrets to be stored from body
+        var b smsbackend.Secret
+        err := json.NewDecoder(r.Body).Decode(&amp;b)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusBadRequest)
+                return
+        }</span>
+
+        <span class="cov10" title="7">err = h.secretBackend.CreateSecret(domName, b)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov10" title="7">w.WriteHeader(http.StatusCreated)</span>
+}
+
+// getSecretHandler handles reading a secret by given domain name and secret name
+func (h handler) getSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
+        vars := mux.Vars(r)
+        domName := vars["domName"]
+        secName := vars["secretName"]
+
+        sec, err := h.secretBackend.GetSecret(domName, secName)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov10" title="7">jdata, err := json.Marshal(sec)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov10" title="7">w.Header().Set("Content-Type", "application/json")
+        w.Write(jdata)</span>
+}
+
+// listSecretHandler handles listing all secrets under a particular domain name
+func (h handler) listSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
+        vars := mux.Vars(r)
+        domName := vars["domName"]
+
+        secList, err := h.secretBackend.ListSecret(domName)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        // Creating an anonymous struct to store the returned list of data
+        <span class="cov6" title="3">var retStruct = struct {
+                SecretNames []string `json:"secretnames"`
+        }{
+                secList,
+        }
+
+        jdata, err := json.Marshal(retStruct)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">w.Header().Set("Content-Type", "application/json")
+        w.Write(jdata)</span>
+}
+
+// deleteSecretHandler handles deleting a secret by given domain name and secret name
+func (h handler) deleteSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
+        vars := mux.Vars(r)
+        domName := vars["domName"]
+        secName := vars["secretName"]
+
+        err := h.secretBackend.DeleteSecret(domName, secName)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+}
+
+// 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) <span class="cov6" title="3">{
+        s, err := h.secretBackend.GetStatus()
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">status := backendStatus{Seal: s}
+        jdata, err := json.Marshal(status)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+
+        <span class="cov6" title="3">w.Header().Set("Content-Type", "application/json")
+        w.Write(jdata)</span>
+}
+
+// loginHandler handles login via password and username
+func (h handler) loginHandler(w http.ResponseWriter, r *http.Request) {<span class="cov0" title="0">
+
+}</span>
+
+// unsealHandler is a pass through that sends requests from quorum client
+// to the backend.
+func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
+        // Get shards to be used for unseal
+        type unsealStruct struct {
+                UnsealShard string `json:"unsealshard"`
+        }
+
+        var inp unsealStruct
+        decoder := json.NewDecoder(r.Body)
+        decoder.DisallowUnknownFields()
+        err := decoder.Decode(&amp;inp)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, "Bad input JSON", http.StatusBadRequest)
+                return
+        }</span>
+
+        <span class="cov0" title="0">err = h.secretBackend.Unseal(inp.UnsealShard)
+        if err != nil </span><span class="cov0" title="0">{
+                smslogger.WriteError(err.Error())
+                http.Error(w, err.Error(), http.StatusInternalServerError)
+                return
+        }</span>
+}
+
+// CreateRouter returns an http.Handler for the registered URLs
+// Takes an interface implementation as input
+func CreateRouter(b smsbackend.SecretBackend) http.Handler <span class="cov4" title="2">{
+        h := handler{secretBackend: b}
+
+        // Create a new mux to handle URL endpoints
+        router := mux.NewRouter()
+
+        router.HandleFunc("/v1/sms/login", h.loginHandler).Methods("POST")
+
+        // 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/domain", h.createSecretDomainHandler).Methods("POST")
+        router.HandleFunc("/v1/sms/domain/{domName}", h.deleteSecretDomainHandler).Methods("DELETE")
+
+        router.HandleFunc("/v1/sms/domain/{domName}/secret", h.createSecretHandler).Methods("POST")
+        router.HandleFunc("/v1/sms/domain/{domName}/secret", h.listSecretHandler).Methods("GET")
+        router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.getSecretHandler).Methods("GET")
+        router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.deleteSecretHandler).Methods("DELETE")
+
+        return router
+}</span>
+</pre>
+               
+               <pre class="file" id="file5" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package log
+
+import (
+        "log"
+        "os"
+)
+
+var errLogger *log.Logger
+var warnLogger *log.Logger
+var infoLogger *log.Logger
+
+// Init will be called by sms.go before any other packages use it
+func Init(filePath string) <span class="cov8" title="1">{
+        f, err := os.Create(filePath)
+        if err != nil </span><span class="cov0" title="0">{
+                log.Println("Unable to create a log file")
+                log.Println(err)
+                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)
+        }</span><span class="cov8" title="1"> else {
+                errLogger = log.New(f, "ERROR: ", log.Lshortfile|log.LstdFlags)
+                warnLogger = log.New(f, "WARNING: ", log.Lshortfile|log.LstdFlags)
+                infoLogger = log.New(f, "INFO: ", log.Lshortfile|log.LstdFlags)
+        }</span>
+}
+
+// WriteError writes output to the writer we have
+// defined durint its creation with ERROR prefix
+func WriteError(msg string) <span class="cov0" title="0">{
+        if errLogger != nil </span><span class="cov0" title="0">{
+                errLogger.Println(msg)
+        }</span>
+}
+
+// WriteWarn writes output to the writer we have
+// defined durint its creation with WARNING prefix
+func WriteWarn(msg string) <span class="cov0" title="0">{
+        if warnLogger != nil </span><span class="cov0" title="0">{
+                warnLogger.Println(msg)
+        }</span>
+}
+
+// WriteInfo writes output to the writer we have
+// defined durint its creation with INFO prefix
+func WriteInfo(msg string) <span class="cov0" title="0">{
+        if infoLogger != nil </span><span class="cov0" title="0">{
+                infoLogger.Println(msg)
+        }</span>
+}
+</pre>
+               
+               <pre class="file" id="file6" style="display: none">/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+        "context"
+        "log"
+        "net/http"
+        "os"
+        "os/signal"
+
+        smsauth "sms/auth"
+        smsbackend "sms/backend"
+        smsconfig "sms/config"
+        smshandler "sms/handler"
+        smslogger "sms/log"
+)
+
+func main() <span class="cov8" title="1">{
+        // Initialize logger
+        smslogger.Init("sms.log")
+
+        // Read Configuration File
+        smsConf, err := smsconfig.ReadConfigFile("smsconfig.json")
+        if err != nil </span><span class="cov0" title="0">{
+                log.Fatal(err)
+        }</span>
+
+        <span class="cov8" title="1">backendImpl, err := smsbackend.InitSecretBackend()
+        if err != nil </span><span class="cov0" title="0">{
+                log.Fatal(err)
+        }</span>
+
+        <span class="cov8" title="1">httpRouter := smshandler.CreateRouter(backendImpl)
+
+        // TODO: Use CA certificate from AAF
+        tlsConfig, err := smsauth.GetTLSConfig(smsConf.CAFile)
+        if err != nil </span><span class="cov0" title="0">{
+                log.Fatal(err)
+        }</span>
+
+        <span class="cov8" title="1">httpServer := &amp;http.Server{
+                Handler:   httpRouter,
+                Addr:      ":10443",
+                TLSConfig: tlsConfig,
+        }
+
+        // Listener for SIGINT so that it returns cleanly
+        connectionsClose := make(chan struct{})
+        go func() </span><span class="cov8" title="1">{
+                c := make(chan os.Signal, 1)
+                signal.Notify(c, os.Interrupt)
+                &lt;-c
+                httpServer.Shutdown(context.Background())
+                close(connectionsClose)
+        }</span>()
+
+        <span class="cov8" title="1">err = httpServer.ListenAndServeTLS(smsConf.ServerCert, smsConf.ServerKey)
+        if err != nil &amp;&amp; err != http.ErrServerClosed </span><span class="cov0" title="0">{
+                log.Fatal(err)
+        }</span>
+
+        <span class="cov8" title="1">&lt;-connectionsClose</span>
+}
+</pre>
+               
+               </div>
+       </body>
+       <script>
+       (function() {
+               var files = document.getElementById('files');
+               var visible;
+               files.addEventListener('change', onChange, false);
+               function select(part) {
+                       if (visible)
+                               visible.style.display = 'none';
+                       visible = document.getElementById(part);
+                       if (!visible)
+                               return;
+                       files.value = part;
+                       visible.style.display = 'block';
+                       location.hash = part;
+               }
+               function onChange() {
+                       select(files.value);
+                       window.scrollTo(0, 0);
+               }
+               if (location.hash != "") {
+                       select(location.hash.substr(1));
+               }
+               if (!visible) {
+                       select("file0");
+               }
+       })();
+       </script>
+</html>
index 12bdfab..b856a81 100644 (file)
@@ -53,7 +53,7 @@
   branch = "master"
   name = "github.com/hashicorp/go-uuid"
   packages = ["."]
-  revision = "64130c7a86d732268a38cb04cfbaf0cc987fda98"
+  revision = "27454136f0364f2d44b1276c552d69105cf8c498"
 
 [[projects]]
   branch = "master"
@@ -69,7 +69,7 @@
     "json/scanner",
     "json/token"
   ]
-  revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8"
+  revision = "f40e974e75af4e271d97ce0fc917af5898ae7bda"
 
 [[projects]]
   name = "github.com/hashicorp/vault"
@@ -80,8 +80,8 @@
     "helper/parseutil",
     "helper/strutil"
   ]
-  revision = "36edb4d42380d89a897e7f633046423240b710d9"
-  version = "v0.9.5"
+  revision = "7e1fbde40afee241f81ef08700e7987d86fc7242"
+  version = "v0.9.6"
 
 [[projects]]
   branch = "master"
@@ -93,7 +93,7 @@
   branch = "master"
   name = "github.com/mitchellh/mapstructure"
   packages = ["."]
-  revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
+  revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
 
 [[projects]]
   name = "github.com/ryanuber/go-glob"
   branch = "master"
   name = "github.com/sethgrid/pester"
   packages = ["."]
-  revision = "760f8913c0483b776294e1bee43f1d687527127b"
+  revision = "ed9870dad3170c0b25ab9b11830cc57c3a7798fb"
 
 [[projects]]
   branch = "master"
     "openpgp/packet",
     "openpgp/s2k"
   ]
-  revision = "85f98707c97e11569271e4d9b3d397e079c4f4d0"
+  revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
 
 [[projects]]
   branch = "master"
     "idna",
     "lex/httplex"
   ]
-  revision = "0ed95abb35c445290478a5348a7b38bb154135fd"
+  revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
 
 [[projects]]
-  branch = "master"
   name = "golang.org/x/text"
   packages = [
     "collate",
     "unicode/norm",
     "unicode/rangetable"
   ]
-  revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
+  revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
+  version = "v0.3.0"
 
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "c3b1a2cc60523cdaccc247048d5e99d9b2f5c5f5e7f66c3e1deb9e709ec6f8bc"
+  inputs-digest = "5bf63627c88decba9287076c33fbda6ac39f0256039375b4002dc35db39c214f"
   solver-name = "gps-cdcl"
   solver-version = 1
index f811086..cab4149 100644 (file)
@@ -26,7 +26,7 @@
 
 [[constraint]]
   name = "github.com/hashicorp/vault"
-  version = "0.9.4"
+  version = "0.9.5"
 
 [[constraint]]
   branch = "master"
diff --git a/sms-service/src/sms/coverage.md b/sms-service/src/sms/coverage.md
new file mode 100644 (file)
index 0000000..6168342
--- /dev/null
@@ -0,0 +1,41 @@
+## Code Coverage Reports for Golang Applications ##
+
+This document covers how to generate HTML Code Coverage Reports for
+Golang Applications.
+
+#### Generate a test executable which calls your main()
+
+```sh
+$ go test -c -covermode=count -coverpkg ./...
+```
+
+#### Run the generated application to produce a new coverage report
+
+```sh
+$ ./sms.test -test.run "^TestMain$" -test.coverprofile=coverage.cov
+```
+
+#### Run your unit tests to produce their coverage report
+
+```sh
+$ go test -test.covermode=count -test.coverprofile=unit.out ./...
+```
+
+#### Merge the two coverage Reports
+
+```sh
+$ go get github.com/wadey/gocovmerge
+$ gocovmerge unit.out coverage.cov > all.out
+```
+
+#### Generate HTML Report
+
+```sh
+$ go tool cover -html all.out -o coverage.html
+```
+
+#### Generate Function Report
+
+```sh
+$ go tool cover -func all.out
+```
\ No newline at end of file
index eb0bebc..de9d0a7 100644 (file)
 package main
 
 import (
+       "context"
        "log"
        "net/http"
+       "os"
+       "os/signal"
 
        smsauth "sms/auth"
        smsbackend "sms/backend"
@@ -56,6 +59,20 @@ func main() {
                TLSConfig: tlsConfig,
        }
 
+       // Listener for SIGINT so that it returns cleanly
+       connectionsClose := make(chan struct{})
+       go func() {
+               c := make(chan os.Signal, 1)
+               signal.Notify(c, os.Interrupt)
+               <-c
+               httpServer.Shutdown(context.Background())
+               close(connectionsClose)
+       }()
+
        err = httpServer.ListenAndServeTLS(smsConf.ServerCert, smsConf.ServerKey)
-       log.Fatal(err)
+       if err != nil && err != http.ErrServerClosed {
+               log.Fatal(err)
+       }
+
+       <-connectionsClose
 }
diff --git a/sms-service/src/sms/sms_test.go b/sms-service/src/sms/sms_test.go
new file mode 100644 (file)
index 0000000..35dce24
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+       "fmt"
+       "os"
+       "testing"
+)
+
+func TestMain(t *testing.T) {
+       fmt.Println(os.Args)
+       t.Log(os.Args)
+       if os.Args[0] == "./sms.test" {
+               main()
+       }
+}