blob: 39ee191d05965797987698322796e10fa404c21a [file] [log] [blame]
<!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 (76.1%)</option>
<option value="file1">sms/backend/backend.go (80.0%)</option>
<option value="file2">sms/backend/vault.go (72.5%)</option>
<option value="file3">sms/config/config.go (78.6%)</option>
<option value="file4">sms/handler/handler.go (63.0%)</option>
<option value="file5">sms/log/logger.go (65.6%)</option>
<option value="file6">sms/sms.go (77.8%)</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"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"io/ioutil"
smslogger "sms/log"
)
// 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
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{
// Change to RequireAndVerify once we have mandatory certs
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
tlsConfig.BuildNameToCertificate()
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="cov10" title="3">{
var entity *openpgp.Entity
config := &amp;packet.Config{
DefaultHash: crypto.SHA256,
}
entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", config)
if smslogger.CheckError(err, "Create Entity") != nil </span><span class="cov0" title="0">{
return "", "", err
}</span>
// Sign the identity in the entity
<span class="cov10" title="3">for _, id := range entity.Identities </span><span class="cov10" title="3">{
err = id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil)
if smslogger.CheckError(err, "Sign Entity") != nil </span><span class="cov0" title="0">{
return "", "", err
}</span>
}
// Sign the subkey in the entity
<span class="cov10" title="3">for _, subkey := range entity.Subkeys </span><span class="cov10" title="3">{
err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, nil)
if smslogger.CheckError(err, "Sign Subkey") != nil </span><span class="cov0" title="0">{
return "", "", err
}</span>
}
<span class="cov10" title="3">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>
}
// EncryptPGPString takes data and a public key and encrypts using that
// public key
func EncryptPGPString(data string, pbKey string) (string, error) <span class="cov6" title="2">{
pbKeyBytes, err := base64.StdEncoding.DecodeString(pbKey)
if smslogger.CheckError(err, "Decoding Base64 Public Key") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov6" title="2">dataBytes := []byte(data)
pbEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(pbKeyBytes)))
if smslogger.CheckError(err, "Reading entity from PGP key") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
// encrypt string
<span class="cov6" title="2">buf := new(bytes.Buffer)
out, err := openpgp.Encrypt(buf, []*openpgp.Entity{pbEntity}, nil, nil, nil)
if smslogger.CheckError(err, "Creating Encryption Pipe") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov6" title="2">_, err = out.Write(dataBytes)
if smslogger.CheckError(err, "Writing to Encryption Pipe") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov6" title="2">err = out.Close()
if smslogger.CheckError(err, "Closing Encryption Pipe") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov6" title="2">crp := base64.StdEncoding.EncodeToString(buf.Bytes())
return crp, nil</span>
}
// DecryptPGPString decrypts a PGP encoded input string and returns
// a base64 representation of the decoded string
func DecryptPGPString(data string, prKey string) (string, error) <span class="cov1" title="1">{
// Convert private key to bytes from base64
prKeyBytes, err := base64.StdEncoding.DecodeString(prKey)
if smslogger.CheckError(err, "Decoding Base64 Private Key") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov1" title="1">dataBytes, err := base64.StdEncoding.DecodeString(data)
if smslogger.CheckError(err, "Decoding base64 data") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov1" title="1">prEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(prKeyBytes)))
if smslogger.CheckError(err, "Read Entity") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov1" title="1">prEntityList := &amp;openpgp.EntityList{prEntity}
message, err := openpgp.ReadMessage(bytes.NewBuffer(dataBytes), prEntityList, nil, nil)
if smslogger.CheckError(err, "Decrypting Message") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov1" title="1">var retBuf bytes.Buffer
retBuf.ReadFrom(message.UnverifiedBody)
return retBuf.String(), nil</span>
}
// ReadFromFile reads a file and loads the PGP key into
// a string
func ReadFromFile(fileName string) (string, error) <span class="cov6" title="2">{
data, err := ioutil.ReadFile(fileName)
if smslogger.CheckError(err, "Read from file") != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov6" title="2">return string(data), nil</span>
}
// WriteToFile writes a PGP key into a file.
// It will truncate the file if it exists
func WriteToFile(data string, fileName string) error <span class="cov0" title="0">{
err := ioutil.WriteFile(fileName, []byte(data), 0600)
if smslogger.CheckError(err, "Write to file") != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov0" title="0">return 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
RegisterQuorum(pgpkey string) (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="cov8" title="1">{
backendImpl := &amp;Vault{
vaultAddress: smsconfig.SMSConfig.BackendAddress,
vaultToken: smsconfig.SMSConfig.VaultToken,
}
err := backendImpl.Init()
if smslogger.CheckError(err, "InitSecretBackend") != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">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
initRoleDone bool
policyName string
roleID string
secretID string
vaultAddress string
vaultClient *vaultapi.Client
vaultMountPrefix string
internalDomain string
internalDomainMounted bool
vaultTempTokenTTL time.Time
vaultToken string
shards []string
prkey string
}
// initVaultClient will create the initial
// Vault strcuture and populate it with the
// right values and it will also create
// a vault client
func (v *Vault) initVaultClient() error <span class="cov6" title="11">{
vaultCFG := vaultapi.DefaultConfig()
vaultCFG.Address = v.vaultAddress
client, err := vaultapi.NewClient(vaultCFG)
if smslogger.CheckError(err, "Create new vault client") != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov6" title="11">v.initRoleDone = false
v.policyName = "smsvaultpolicy"
v.vaultClient = client
v.vaultMountPrefix = "sms"
v.internalDomain = "smsinternaldomain"
v.internalDomainMounted = false
v.prkey = ""
return nil</span>
}
// Init will initialize the vault connection
// It will also initialize vault if it is not
// already initialized.
// The initial policy will also be created
func (v *Vault) Init() error <span class="cov1" title="1">{
v.initVaultClient()
// Initialize vault if it is not already
// Returns immediately if it is initialized
v.initializeVault()
err := v.initRole()
if smslogger.CheckError(err, "InitRole First Attempt") != nil </span><span class="cov0" title="0">{
smslogger.WriteInfo("InitRole will try again later")
}</span>
<span class="cov1" title="1">return nil</span>
}
// GetStatus returns the current seal status of vault
func (v *Vault) GetStatus() (bool, error) <span class="cov3" title="3">{
sys := v.vaultClient.Sys()
sealStatus, err := sys.SealStatus()
if smslogger.CheckError(err, "Getting Status") != nil </span><span class="cov0" title="0">{
return false, errors.New("Error getting status")
}</span>
<span class="cov3" title="3">return sealStatus.Sealed, nil</span>
}
// RegisterQuorum registers the PGP public key for a quorum client
// We will return a shard to the client that is registering
func (v *Vault) RegisterQuorum(pgpkey string) (string, error) <span class="cov0" title="0">{
v.Lock()
defer v.Unlock()
if v.shards == nil </span><span class="cov0" title="0">{
smslogger.WriteError("Invalid operation in RegisterQuorum")
return "", errors.New("Invalid operation")
}</span>
// Pop the slice
<span class="cov0" title="0">var sh string
sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1]
if len(v.shards) == 0 </span><span class="cov0" title="0">{
v.shards = nil
}</span>
// Decrypt with SMS pgp Key
<span class="cov0" title="0">sh, _ = smsauth.DecryptPGPString(sh, v.prkey)
// Encrypt with Quorum client pgp key
sh, _ = smsauth.EncryptPGPString(sh, pgpkey)
return sh, 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 smslogger.CheckError(err, "Unseal Operation") != nil </span><span class="cov0" title="0">{
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="cov5" title="7">{
err := v.checkToken()
if smslogger.CheckError(err, "Tocken Check") != nil </span><span class="cov0" title="0">{
return Secret{}, errors.New("Token check failed")
}</span>
<span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom
sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{
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="cov5" title="7">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="cov5" title="7">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="cov3" title="3">{
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return nil, errors.New("Token check failed")
}</span>
<span class="cov3" title="3">dom = v.vaultMountPrefix + "/" + dom
sec, err := v.vaultClient.Logical().List(dom)
if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{
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="cov3" title="3">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="cov3" title="3">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="cov3" title="3">retval := make([]string, len(val))
for i, v := range val </span><span class="cov5" title="7">{
retval[i] = fmt.Sprint(v)
}</span>
<span class="cov3" title="3">return retval, nil</span>
}
// Mounts the internal Domain if its not already mounted
func (v *Vault) mountInternalDomain(name string) error <span class="cov5" title="8">{
if v.internalDomainMounted </span><span class="cov1" title="1">{
return nil
}</span>
<span class="cov5" title="7">name = strings.TrimSpace(name)
mountPath := v.vaultMountPrefix + "/" + name
mountInput := &amp;vaultapi.MountInput{
Type: "kv",
Description: "Mount point for domain: " + name,
Local: false,
SealWrap: false,
Config: vaultapi.MountConfigInput{},
}
err := v.vaultClient.Sys().Mount(mountPath, mountInput)
if smslogger.CheckError(err, "Mount internal Domain") != nil </span><span class="cov1" title="1">{
if strings.Contains(err.Error(), "existing mount") </span><span class="cov1" title="1">{
// It is already mounted
v.internalDomainMounted = true
return nil
}</span>
// Ran into some other error mounting it.
<span class="cov0" title="0">return errors.New("Unable to mount internal Domain")</span>
}
<span class="cov5" title="6">v.internalDomainMounted = true
return nil</span>
}
// Stores the UUID created for secretdomain in vault
// under v.vaultMountPrefix / smsinternal domain
func (v *Vault) storeUUID(uuid string, name string) error <span class="cov5" title="8">{
// Check if token is still valid
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return errors.New("Token Check failed")
}</span>
<span class="cov5" title="8">err = v.mountInternalDomain(v.internalDomain)
if smslogger.CheckError(err, "Mount Internal Domain") != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov5" title="8">secret := Secret{
Name: name,
Values: map[string]interface{}{
"uuid": uuid,
},
}
err = v.CreateSecret(v.internalDomain, secret)
if smslogger.CheckError(err, "Write UUID to domain") != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov5" title="8">return nil</span>
}
// CreateSecretDomain mounts the kv backend on a path with the given name
func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) <span class="cov5" title="8">{
// Check if token is still valid
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return SecretDomain{}, errors.New("Token Check failed")
}</span>
<span class="cov5" title="8">name = strings.TrimSpace(name)
mountPath := v.vaultMountPrefix + "/" + name
mountInput := &amp;vaultapi.MountInput{
Type: "kv",
Description: "Mount point for domain: " + name,
Local: false,
SealWrap: false,
Config: vaultapi.MountConfigInput{},
}
err = v.vaultClient.Sys().Mount(mountPath, mountInput)
if smslogger.CheckError(err, "Create Domain") != nil </span><span class="cov0" title="0">{
return SecretDomain{}, errors.New("Unable to create Secret Domain")
}</span>
<span class="cov5" title="8">uuid, _ := uuid.GenerateUUID()
err = v.storeUUID(uuid, name)
if smslogger.CheckError(err, "Store UUID") != nil </span><span class="cov0" title="0">{
// Mount was successful at this point.
// Rollback the mount operation since we could not
// store the UUID for the mount.
v.vaultClient.Sys().Unmount(mountPath)
return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
}</span>
<span class="cov5" title="8">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="cov7" title="18">{
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return errors.New("Token check failed")
}</span>
<span class="cov7" title="18">dom = v.vaultMountPrefix + "/" + 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 smslogger.CheckError(err, "Create Secret") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to create Secret at provided path")
}</span>
<span class="cov7" title="18">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="cov3" title="3">{
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return errors.New("Token Check Failed")
}</span>
<span class="cov3" title="3">name = strings.TrimSpace(name)
mountPath := v.vaultMountPrefix + "/" + name
err = v.vaultClient.Sys().Unmount(mountPath)
if smslogger.CheckError(err, "Delete Domain") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to delete domain specified")
}</span>
<span class="cov3" title="3">return nil</span>
}
// DeleteSecret deletes a secret mounted on the path provided
func (v *Vault) DeleteSecret(dom string, name string) error <span class="cov5" title="7">{
err := v.checkToken()
if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
return errors.New("Token check failed")
}</span>
<span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom
// Vault return is empty on successful delete
_, err = v.vaultClient.Logical().Delete(dom + "/" + name)
if smslogger.CheckError(err, "Delete Secret") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to delete Secret at provided path")
}</span>
<span class="cov5" title="7">return nil</span>
}
// initRole is called only once during SMS bring up
// It initially creates a role and secret id associated with
// that role. Later restarts will use the existing role-id
// and secret-id stored on disk
func (v *Vault) initRole() error <span class="cov10" title="56">{
if v.initRoleDone </span><span class="cov9" title="48">{
return nil
}</span>
// Use the root token once here
<span class="cov5" title="8">v.vaultClient.SetToken(v.vaultToken)
defer v.vaultClient.ClearToken()
// Check if roleID and secretID has already been created
rID, error := smsauth.ReadFromFile("auth/role")
if error != nil </span><span class="cov5" title="7">{
smslogger.WriteWarn("Unable to find RoleID. Generating...")
}</span><span class="cov1" title="1"> else {
sID, error := smsauth.ReadFromFile("auth/secret")
if error != nil </span><span class="cov0" title="0">{
smslogger.WriteWarn("Unable to find secretID. Generating...")
}</span><span class="cov1" title="1"> else {
v.roleID = rID
v.secretID = sID
v.initRoleDone = true
return nil
}</span>
}
<span class="cov5" title="7">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 smslogger.CheckError(err, "Creating Policy") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to create policy for approle creation")
}</span>
//Check if applrole is mounted
<span class="cov5" title="7">authMounts, err := v.vaultClient.Sys().ListAuth()
if smslogger.CheckError(err, "Mount Auth Backend") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to get mounted auth backends")
}</span>
<span class="cov5" title="7">approleMounted := false
for k, v := range authMounts </span><span class="cov5" title="7">{
if v.Type == "approle" &amp;&amp; k == "approle/" </span><span class="cov0" title="0">{
approleMounted = true
break</span>
}
}
// Mount approle in case its not already mounted
<span class="cov5" title="7">if !approleMounted </span><span class="cov5" title="7">{
v.vaultClient.Sys().EnableAuth("approle", "approle", "")
}</span>
<span class="cov5" title="7">rName := v.vaultMountPrefix + "-role"
data := map[string]interface{}{
"token_ttl": "60m",
"policies": [2]string{"default", v.policyName},
}
// Create a role-id
v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
if smslogger.CheckError(err, "Create RoleID") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to create role ID for approle")
}</span>
<span class="cov5" title="7">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 smslogger.CheckError(err, "Create SecretID") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to create secret ID for role")
}</span>
<span class="cov5" title="7">v.secretID = sec.Data["secret_id"].(string)
v.initRoleDone = true
/*
* Revoke the Root token.
* If a new Root Token is needed, it will need to be created
* using the unseal shards.
*/
err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
if smslogger.CheckError(err, "Revoke Root Token") != nil </span><span class="cov0" title="0">{
smslogger.WriteWarn("Unable to Revoke Token")
}</span><span class="cov5" title="7"> else {
// Revoked successfully and clear it
v.vaultToken = ""
}</span>
// Store the role-id and secret-id
// We will need this if SMS restarts
<span class="cov5" title="7">smsauth.WriteToFile(v.roleID, "auth/role")
smsauth.WriteToFile(v.secretID, "auth/secret")
return nil</span>
}
// Function checkToken() gets called multiple times to create
// temporary tokens
func (v *Vault) checkToken() error <span class="cov9" title="54">{
v.Lock()
defer v.Unlock()
// Init Role if it is not yet done
// Role needs to be created before token can be created
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="cov9" title="54">if v.vaultClient.Token() != "" &amp;&amp;
time.Since(v.vaultTempTokenTTL) &lt; time.Minute*50 </span><span class="cov9" title="47">{
return nil
}</span>
// Create a temporary token using our roleID and secretID
<span class="cov5" title="7">out, err := v.vaultClient.Logical().Write("auth/approle/login",
map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
if smslogger.CheckError(err, "Create Temp Token") != nil </span><span class="cov0" title="0">{
return errors.New("Unable to create Temporary Token for Role")
}</span>
<span class="cov5" title="7">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="cov2" title="2">{
// Check for vault init status and don't exit till it is initialized
for </span><span class="cov2" title="2">{
init, err := v.vaultClient.Sys().InitStatus()
if smslogger.CheckError(err, "Get Vault Init Status") != nil </span><span class="cov0" title="0">{
smslogger.WriteInfo("Trying again in 10s...")
time.Sleep(time.Second * 10)
continue</span>
}
// Did not get any error
<span class="cov2" title="2">if init == true </span><span class="cov1" title="1">{
smslogger.WriteInfo("Vault is already Initialized")
return nil
}</span>
// init status is false
// break out of loop and finish initialization
<span class="cov1" title="1">smslogger.WriteInfo("Vault is not initialized. Initializing...")
break</span>
}
// Hardcoded this to 3. We should make this configurable
// in the future
<span class="cov1" title="1">initReq := &amp;vaultapi.InitRequest{
SecretShares: 3,
SecretThreshold: 3,
}
pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
if smslogger.CheckError(err, "Generating PGP Keys") != nil </span><span class="cov0" title="0">{
smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
}</span><span class="cov1" title="1"> else {
initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
initReq.RootTokenPGPKey = pbkey
}</span>
<span class="cov1" title="1">resp, err := v.vaultClient.Sys().Init(initReq)
if smslogger.CheckError(err, "Initialize Vault") != nil </span><span class="cov0" title="0">{
return errors.New("FATAL: Unable to initialize Vault")
}</span>
<span class="cov1" title="1">if resp != nil </span><span class="cov1" title="1">{
v.prkey = prkey
v.shards = resp.KeysB64
v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
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"
smslogger "sms/log"
)
// 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"`
BackendAddress string `json:"smsdbaddress"`
VaultToken string `json:"vaulttoken"`
DisableTLS bool `json:"disable_tls"`
BackendAddressEnvVariable string `json:"smsdburlenv"`
}
// 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()
// Default behaviour is to enable TLS
SMSConfig = &amp;SMSConfiguration{DisableTLS: false}
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">if SMSConfig.BackendAddress == "" &amp;&amp; SMSConfig.BackendAddressEnvVariable != "" </span><span class="cov0" title="0">{
// Get the value from ENV variable
smslogger.WriteInfo("Using Environment Variable: " + SMSConfig.BackendAddressEnvVariable)
SMSConfig.BackendAddress = os.Getenv(SMSConfig.BackendAddressEnvVariable)
}</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 smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusBadRequest)
return
}</span>
<span class="cov6" title="3">dom, err := h.secretBackend.CreateSecretDomain(d.Name)
if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
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)
err = json.NewEncoder(w).Encode(dom)
if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</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 smslogger.CheckError(err, "DeleteSecretDomainHandler") != nil </span><span class="cov0" title="0">{
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 smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusBadRequest)
return
}</span>
<span class="cov10" title="7">err = h.secretBackend.CreateSecret(domName, b)
if smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{
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 smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</span>
<span class="cov10" title="7">w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(sec)
if smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</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 smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{
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,
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(retStruct)
if smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</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 smslogger.CheckError(err, "DeleteSecretHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</span>
<span class="cov10" title="7">w.WriteHeader(http.StatusNoContent)</span>
}
// statusHandler returns information related to SMS and SMS backend services
func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) <span class="cov7" title="4">{
s, err := h.secretBackend.GetStatus()
if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</span>
<span class="cov7" title="4">status := struct {
Seal bool `json:"sealstatus"`
}{
s,
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(status)
if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</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 smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, "Bad input JSON", http.StatusBadRequest)
return
}</span>
<span class="cov0" title="0">err = h.secretBackend.Unseal(inp.UnsealShard)
if smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</span>
}
// registerHandler allows the quorum clients to register with SMS
// with their PGP public keys that are then used by sms for backend
// initialization
func (h handler) registerHandler(w http.ResponseWriter, r *http.Request) <span class="cov1" title="1">{
// Get shards to be used for unseal
type registerStruct struct {
PGPKey string `json:"pgpkey"`
QuorumID string `json:"quorumid"`
}
var inp registerStruct
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
err := decoder.Decode(&amp;inp)
if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, "Bad input JSON", http.StatusBadRequest)
return
}</span>
<span class="cov1" title="1">sh, err := h.secretBackend.RegisterQuorum(inp.PGPKey)
if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}</span>
// Creating a struct for return data
<span class="cov1" title="1">shStruct := struct {
Shard string `json:"shard"`
}{
sh,
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(shStruct)
if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
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/quorum/status", h.statusHandler).Methods("GET")
router.HandleFunc("/v1/sms/quorum/unseal", h.unsealHandler).Methods("POST")
router.HandleFunc("/v1/sms/quorum/register", h.registerHandler).Methods("POST")
router.HandleFunc("/v1/sms/domain", h.createSecretDomainHandler).Methods("POST")
router.HandleFunc("/v1/sms/domain/{domName}", h.deleteSecretDomainHandler).Methods("DELETE")
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 (
"fmt"
"log"
"os"
)
var errL, warnL, infoL *log.Logger
var stdErr, stdWarn, stdInfo *log.Logger
// Init will be called by sms.go before any other packages use it
func Init(filePath string) <span class="cov1" title="1">{
stdErr = log.New(os.Stderr, "ERROR: ", log.Lshortfile|log.LstdFlags)
stdWarn = log.New(os.Stdout, "WARNING: ", log.Lshortfile|log.LstdFlags)
stdInfo = log.New(os.Stdout, "INFO: ", log.Lshortfile|log.LstdFlags)
if filePath == "" </span><span class="cov0" title="0">{
// We will just to std streams
return
}</span>
<span class="cov1" title="1">f, err := os.Create(filePath)
if err != nil </span><span class="cov0" title="0">{
stdErr.Println("Unable to create log file: " + err.Error())
return
}</span>
<span class="cov1" title="1">errL = log.New(f, "ERROR: ", log.Lshortfile|log.LstdFlags)
warnL = log.New(f, "WARNING: ", log.Lshortfile|log.LstdFlags)
infoL = log.New(f, "INFO: ", log.Lshortfile|log.LstdFlags)</span>
}
// WriteError writes output to the writer we have
// defined during its creation with ERROR prefix
func WriteError(msg string) <span class="cov0" title="0">{
if errL != nil </span><span class="cov0" title="0">{
errL.Output(2, fmt.Sprintln(msg))
}</span>
<span class="cov0" title="0">if stdErr != nil </span><span class="cov0" title="0">{
stdErr.Output(2, fmt.Sprintln(msg))
}</span>
}
// WriteWarn writes output to the writer we have
// defined during its creation with WARNING prefix
func WriteWarn(msg string) <span class="cov0" title="0">{
if warnL != nil </span><span class="cov0" title="0">{
warnL.Output(2, fmt.Sprintln(msg))
}</span>
<span class="cov0" title="0">if stdWarn != nil </span><span class="cov0" title="0">{
stdWarn.Output(2, fmt.Sprintln(msg))
}</span>
}
// WriteInfo writes output to the writer we have
// defined during its creation with INFO prefix
func WriteInfo(msg string) <span class="cov1" title="1">{
if infoL != nil </span><span class="cov1" title="1">{
infoL.Output(2, fmt.Sprintln(msg))
}</span>
<span class="cov1" title="1">if stdInfo != nil </span><span class="cov1" title="1">{
stdInfo.Output(2, fmt.Sprintln(msg))
}</span>
}
//CheckError is a helper function to reduce
//repitition of error checkign blocks of code
func CheckError(err error, topic string) error <span class="cov10" title="116">{
if err != nil </span><span class="cov1" title="1">{
msg := topic + ": " + err.Error()
if errL != nil </span><span class="cov1" title="1">{
errL.Output(2, fmt.Sprintln(msg))
}</span>
<span class="cov1" title="1">if stdErr != nil </span><span class="cov1" title="1">{
stdErr.Output(2, fmt.Sprintln(msg))
}</span>
<span class="cov1" title="1">return err</span>
}
<span class="cov9" title="115">return nil</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)
httpServer := &amp;http.Server{
Handler: httpRouter,
Addr: ":10443",
}
// 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>()
// Start in TLS mode by default
<span class="cov8" title="1">if smsConf.DisableTLS == true </span><span class="cov0" title="0">{
smslogger.WriteWarn("TLS is Disabled")
err = httpServer.ListenAndServe()
}</span><span class="cov8" title="1"> else {
// 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.TLSConfig = tlsConfig
err = httpServer.ListenAndServeTLS(smsConf.ServerCert, smsConf.ServerKey)</span>
}
<span class="cov8" title="1">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>