summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKiran Kamineni <kiran.k.kamineni@intel.com>2018-03-23 14:14:10 -0700
committerKiran Kamineni <kiran.k.kamineni@intel.com>2018-03-23 14:17:47 -0700
commit937a0cdf641aa66ba8cf413f684d67dc3a9fd90d (patch)
tree9bb2638b18ca96596d568f384bf221296551e660
parent7cdae555806b7fb642460845961295992c6f7f34 (diff)
Adding system test coverage support
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>
-rw-r--r--sms-service/doc/coverage.html1182
-rw-r--r--sms-service/src/sms/Gopkg.lock22
-rw-r--r--sms-service/src/sms/Gopkg.toml2
-rw-r--r--sms-service/src/sms/coverage.md41
-rw-r--r--sms-service/src/sms/sms.go19
-rw-r--r--sms-service/src/sms/sms_test.go31
6 files changed, 1284 insertions, 13 deletions
diff --git a/sms-service/doc/coverage.html b/sms-service/doc/coverage.html
new file mode 100644
index 0000000..d03ddde
--- /dev/null
+++ b/sms-service/doc/coverage.html
@@ -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>
diff --git a/sms-service/src/sms/Gopkg.lock b/sms-service/src/sms/Gopkg.lock
index 12bdfab..b856a81 100644
--- a/sms-service/src/sms/Gopkg.lock
+++ b/sms-service/src/sms/Gopkg.lock
@@ -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"
@@ -105,7 +105,7 @@
branch = "master"
name = "github.com/sethgrid/pester"
packages = ["."]
- revision = "760f8913c0483b776294e1bee43f1d687527127b"
+ revision = "ed9870dad3170c0b25ab9b11830cc57c3a7798fb"
[[projects]]
branch = "master"
@@ -119,7 +119,7 @@
"openpgp/packet",
"openpgp/s2k"
]
- revision = "85f98707c97e11569271e4d9b3d397e079c4f4d0"
+ revision = "88942b9c40a4c9d203b82b3731787b672d6e809b"
[[projects]]
branch = "master"
@@ -130,10 +130,9 @@
"idna",
"lex/httplex"
]
- revision = "0ed95abb35c445290478a5348a7b38bb154135fd"
+ revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
[[projects]]
- branch = "master"
name = "golang.org/x/text"
packages = [
"collate",
@@ -151,11 +150,12 @@
"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
diff --git a/sms-service/src/sms/Gopkg.toml b/sms-service/src/sms/Gopkg.toml
index f811086..cab4149 100644
--- a/sms-service/src/sms/Gopkg.toml
+++ b/sms-service/src/sms/Gopkg.toml
@@ -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
index 0000000..6168342
--- /dev/null
+++ b/sms-service/src/sms/coverage.md
@@ -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
diff --git a/sms-service/src/sms/sms.go b/sms-service/src/sms/sms.go
index eb0bebc..de9d0a7 100644
--- a/sms-service/src/sms/sms.go
+++ b/sms-service/src/sms/sms.go
@@ -17,8 +17,11 @@
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
index 0000000..35dce24
--- /dev/null
+++ b/sms-service/src/sms/sms_test.go
@@ -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()
+ }
+}