5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
9 color: rgb(80, 80, 80);
11 body, pre, #legend span {
12 font-family: Menlo, monospace;
18 top: 0; left: 0; right: 0;
20 border-bottom: 1px solid rgb(80, 80, 80);
38 .cov0 { color: rgb(192, 0, 0) }
39 .cov1 { color: rgb(128, 128, 128) }
40 .cov2 { color: rgb(116, 140, 131) }
41 .cov3 { color: rgb(104, 152, 134) }
42 .cov4 { color: rgb(92, 164, 137) }
43 .cov5 { color: rgb(80, 176, 140) }
44 .cov6 { color: rgb(68, 188, 143) }
45 .cov7 { color: rgb(56, 200, 146) }
46 .cov8 { color: rgb(44, 212, 149) }
47 .cov9 { color: rgb(32, 224, 152) }
48 .cov10 { color: rgb(20, 236, 155) }
57 <option value="file0">sms/auth/auth.go (76.1%)</option>
59 <option value="file1">sms/backend/backend.go (80.0%)</option>
61 <option value="file2">sms/backend/vault.go (72.5%)</option>
63 <option value="file3">sms/config/config.go (78.6%)</option>
65 <option value="file4">sms/handler/handler.go (63.0%)</option>
67 <option value="file5">sms/log/logger.go (65.6%)</option>
69 <option value="file6">sms/sms.go (77.8%)</option>
74 <span>not tracked</span>
76 <span class="cov0">no coverage</span>
77 <span class="cov1">low coverage</span>
78 <span class="cov2">*</span>
79 <span class="cov3">*</span>
80 <span class="cov4">*</span>
81 <span class="cov5">*</span>
82 <span class="cov6">*</span>
83 <span class="cov7">*</span>
84 <span class="cov8">*</span>
85 <span class="cov9">*</span>
86 <span class="cov10">high coverage</span>
92 <pre class="file" id="file0" style="display: none">/*
93 * Copyright 2018 Intel Corporation, Inc
95 * Licensed under the Apache License, Version 2.0 (the "License");
96 * you may not use this file except in compliance with the License.
97 * You may obtain a copy of the License at
99 * http://www.apache.org/licenses/LICENSE-2.0
101 * Unless required by applicable law or agreed to in writing, software
102 * distributed under the License is distributed on an "AS IS" BASIS,
103 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
104 * See the License for the specific language governing permissions and
105 * limitations under the License.
116 "golang.org/x/crypto/openpgp"
117 "golang.org/x/crypto/openpgp/packet"
123 // GetTLSConfig initializes a tlsConfig using the CA's certificate
124 // This config is then used to enable the server for mutual TLS
125 func GetTLSConfig(caCertFile string) (*tls.Config, error) <span class="cov10" title="3">{
127 // Initialize tlsConfig once
128 caCert, err := ioutil.ReadFile(caCertFile)
130 if err != nil </span><span class="cov1" title="1">{
134 <span class="cov6" title="2">caCertPool := x509.NewCertPool()
135 caCertPool.AppendCertsFromPEM(caCert)
137 tlsConfig := &tls.Config{
138 // Change to RequireAndVerify once we have mandatory certs
139 ClientAuth: tls.VerifyClientCertIfGiven,
140 ClientCAs: caCertPool,
141 MinVersion: tls.VersionTLS12,
143 tlsConfig.BuildNameToCertificate()
144 return tlsConfig, nil</span>
147 // GeneratePGPKeyPair produces a PGP key pair and returns
149 // A base64 encoded form of the public part of the entity
150 // A base64 encoded form of the private key
151 func GeneratePGPKeyPair() (string, string, error) <span class="cov10" title="3">{
153 var entity *openpgp.Entity
154 config := &packet.Config{
155 DefaultHash: crypto.SHA256,
158 entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", config)
159 if smslogger.CheckError(err, "Create Entity") != nil </span><span class="cov0" title="0">{
163 // Sign the identity in the entity
164 <span class="cov10" title="3">for _, id := range entity.Identities </span><span class="cov10" title="3">{
165 err = id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil)
166 if smslogger.CheckError(err, "Sign Entity") != nil </span><span class="cov0" title="0">{
171 // Sign the subkey in the entity
172 <span class="cov10" title="3">for _, subkey := range entity.Subkeys </span><span class="cov10" title="3">{
173 err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, nil)
174 if smslogger.CheckError(err, "Sign Subkey") != nil </span><span class="cov0" title="0">{
179 <span class="cov10" title="3">buffer := new(bytes.Buffer)
180 entity.Serialize(buffer)
181 pbkey := base64.StdEncoding.EncodeToString(buffer.Bytes())
184 entity.SerializePrivate(buffer, nil)
185 prkey := base64.StdEncoding.EncodeToString(buffer.Bytes())
187 return pbkey, prkey, nil</span>
190 // EncryptPGPString takes data and a public key and encrypts using that
192 func EncryptPGPString(data string, pbKey string) (string, error) <span class="cov6" title="2">{
194 pbKeyBytes, err := base64.StdEncoding.DecodeString(pbKey)
195 if smslogger.CheckError(err, "Decoding Base64 Public Key") != nil </span><span class="cov0" title="0">{
199 <span class="cov6" title="2">dataBytes := []byte(data)
201 pbEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(pbKeyBytes)))
202 if smslogger.CheckError(err, "Reading entity from PGP key") != nil </span><span class="cov0" title="0">{
207 <span class="cov6" title="2">buf := new(bytes.Buffer)
208 out, err := openpgp.Encrypt(buf, []*openpgp.Entity{pbEntity}, nil, nil, nil)
209 if smslogger.CheckError(err, "Creating Encryption Pipe") != nil </span><span class="cov0" title="0">{
213 <span class="cov6" title="2">_, err = out.Write(dataBytes)
214 if smslogger.CheckError(err, "Writing to Encryption Pipe") != nil </span><span class="cov0" title="0">{
218 <span class="cov6" title="2">err = out.Close()
219 if smslogger.CheckError(err, "Closing Encryption Pipe") != nil </span><span class="cov0" title="0">{
223 <span class="cov6" title="2">crp := base64.StdEncoding.EncodeToString(buf.Bytes())
224 return crp, nil</span>
227 // DecryptPGPString decrypts a PGP encoded input string and returns
228 // a base64 representation of the decoded string
229 func DecryptPGPString(data string, prKey string) (string, error) <span class="cov1" title="1">{
231 // Convert private key to bytes from base64
232 prKeyBytes, err := base64.StdEncoding.DecodeString(prKey)
233 if smslogger.CheckError(err, "Decoding Base64 Private Key") != nil </span><span class="cov0" title="0">{
237 <span class="cov1" title="1">dataBytes, err := base64.StdEncoding.DecodeString(data)
238 if smslogger.CheckError(err, "Decoding base64 data") != nil </span><span class="cov0" title="0">{
242 <span class="cov1" title="1">prEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(prKeyBytes)))
243 if smslogger.CheckError(err, "Read Entity") != nil </span><span class="cov0" title="0">{
247 <span class="cov1" title="1">prEntityList := &openpgp.EntityList{prEntity}
248 message, err := openpgp.ReadMessage(bytes.NewBuffer(dataBytes), prEntityList, nil, nil)
249 if smslogger.CheckError(err, "Decrypting Message") != nil </span><span class="cov0" title="0">{
253 <span class="cov1" title="1">var retBuf bytes.Buffer
254 retBuf.ReadFrom(message.UnverifiedBody)
256 return retBuf.String(), nil</span>
259 // ReadFromFile reads a file and loads the PGP key into
261 func ReadFromFile(fileName string) (string, error) <span class="cov6" title="2">{
263 data, err := ioutil.ReadFile(fileName)
264 if smslogger.CheckError(err, "Read from file") != nil </span><span class="cov0" title="0">{
267 <span class="cov6" title="2">return string(data), nil</span>
270 // WriteToFile writes a PGP key into a file.
271 // It will truncate the file if it exists
272 func WriteToFile(data string, fileName string) error <span class="cov0" title="0">{
274 err := ioutil.WriteFile(fileName, []byte(data), 0600)
275 if smslogger.CheckError(err, "Write to file") != nil </span><span class="cov0" title="0">{
278 <span class="cov0" title="0">return nil</span>
282 <pre class="file" id="file1" style="display: none">/*
283 * Copyright 2018 Intel Corporation, Inc
285 * Licensed under the Apache License, Version 2.0 (the "License");
286 * you may not use this file except in compliance with the License.
287 * You may obtain a copy of the License at
289 * http://www.apache.org/licenses/LICENSE-2.0
291 * Unless required by applicable law or agreed to in writing, software
292 * distributed under the License is distributed on an "AS IS" BASIS,
293 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
294 * See the License for the specific language governing permissions and
295 * limitations under the License.
301 smsconfig "sms/config"
305 // SecretDomain is where Secrets are stored.
306 // A single domain can have any number of secrets
307 type SecretDomain struct {
308 UUID string `json:"uuid"`
309 Name string `json:"name"`
312 // Secret is the struct that defines the structure of a secret
313 // It consists of a name and map containing key value pairs
315 Name string `json:"name"`
316 Values map[string]interface{} `json:"values"`
319 // SecretBackend interface that will be implemented for various secret backends
320 type SecretBackend interface {
322 GetStatus() (bool, error)
323 Unseal(shard string) error
324 RegisterQuorum(pgpkey string) (string, error)
326 GetSecret(dom string, sec string) (Secret, error)
327 ListSecret(dom string) ([]string, error)
329 CreateSecretDomain(name string) (SecretDomain, error)
330 CreateSecret(dom string, sec Secret) error
332 DeleteSecretDomain(name string) error
333 DeleteSecret(dom string, name string) error
336 // InitSecretBackend returns an interface implementation
337 func InitSecretBackend() (SecretBackend, error) <span class="cov8" title="1">{
338 backendImpl := &Vault{
339 vaultAddress: smsconfig.SMSConfig.BackendAddress,
340 vaultToken: smsconfig.SMSConfig.VaultToken,
343 err := backendImpl.Init()
344 if smslogger.CheckError(err, "InitSecretBackend") != nil </span><span class="cov0" title="0">{
348 <span class="cov8" title="1">return backendImpl, nil</span>
351 // LoginBackend Interface that will be implemented for various login backends
352 type LoginBackend interface {
356 <pre class="file" id="file2" style="display: none">/*
357 * Copyright 2018 Intel Corporation, Inc
359 * Licensed under the Apache License, Version 2.0 (the "License");
360 * you may not use this file except in compliance with the License.
361 * You may obtain a copy of the License at
363 * http://www.apache.org/licenses/LICENSE-2.0
365 * Unless required by applicable law or agreed to in writing, software
366 * distributed under the License is distributed on an "AS IS" BASIS,
367 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
368 * See the License for the specific language governing permissions and
369 * limitations under the License.
375 uuid "github.com/hashicorp/go-uuid"
376 vaultapi "github.com/hashicorp/vault/api"
387 // Vault is the main Struct used in Backend to initialize the struct
395 vaultClient *vaultapi.Client
396 vaultMountPrefix string
397 internalDomain string
398 internalDomainMounted bool
399 vaultTempTokenTTL time.Time
405 // initVaultClient will create the initial
406 // Vault strcuture and populate it with the
407 // right values and it will also create
409 func (v *Vault) initVaultClient() error <span class="cov6" title="11">{
411 vaultCFG := vaultapi.DefaultConfig()
412 vaultCFG.Address = v.vaultAddress
413 client, err := vaultapi.NewClient(vaultCFG)
414 if smslogger.CheckError(err, "Create new vault client") != nil </span><span class="cov0" title="0">{
418 <span class="cov6" title="11">v.initRoleDone = false
419 v.policyName = "smsvaultpolicy"
420 v.vaultClient = client
421 v.vaultMountPrefix = "sms"
422 v.internalDomain = "smsinternaldomain"
423 v.internalDomainMounted = false
428 // Init will initialize the vault connection
429 // It will also initialize vault if it is not
430 // already initialized.
431 // The initial policy will also be created
432 func (v *Vault) Init() error <span class="cov1" title="1">{
435 // Initialize vault if it is not already
436 // Returns immediately if it is initialized
440 if smslogger.CheckError(err, "InitRole First Attempt") != nil </span><span class="cov0" title="0">{
441 smslogger.WriteInfo("InitRole will try again later")
444 <span class="cov1" title="1">return nil</span>
447 // GetStatus returns the current seal status of vault
448 func (v *Vault) GetStatus() (bool, error) <span class="cov3" title="3">{
450 sys := v.vaultClient.Sys()
451 sealStatus, err := sys.SealStatus()
452 if smslogger.CheckError(err, "Getting Status") != nil </span><span class="cov0" title="0">{
453 return false, errors.New("Error getting status")
456 <span class="cov3" title="3">return sealStatus.Sealed, nil</span>
459 // RegisterQuorum registers the PGP public key for a quorum client
460 // We will return a shard to the client that is registering
461 func (v *Vault) RegisterQuorum(pgpkey string) (string, error) <span class="cov0" title="0">{
466 if v.shards == nil </span><span class="cov0" title="0">{
467 smslogger.WriteError("Invalid operation in RegisterQuorum")
468 return "", errors.New("Invalid operation")
471 <span class="cov0" title="0">var sh string
472 sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1]
473 if len(v.shards) == 0 </span><span class="cov0" title="0">{
477 // Decrypt with SMS pgp Key
478 <span class="cov0" title="0">sh, _ = smsauth.DecryptPGPString(sh, v.prkey)
479 // Encrypt with Quorum client pgp key
480 sh, _ = smsauth.EncryptPGPString(sh, pgpkey)
482 return sh, nil</span>
485 // Unseal is a passthrough API that allows any
486 // unseal or initialization processes for the backend
487 func (v *Vault) Unseal(shard string) error <span class="cov0" title="0">{
489 sys := v.vaultClient.Sys()
490 _, err := sys.Unseal(shard)
491 if smslogger.CheckError(err, "Unseal Operation") != nil </span><span class="cov0" title="0">{
492 return errors.New("Unable to execute unseal operation with specified shard")
495 <span class="cov0" title="0">return nil</span>
498 // GetSecret returns a secret mounted on a particular domain name
499 // The secret itself is referenced via its name which translates to
500 // a mount path in vault
501 func (v *Vault) GetSecret(dom string, name string) (Secret, error) <span class="cov5" title="7">{
503 err := v.checkToken()
504 if smslogger.CheckError(err, "Tocken Check") != nil </span><span class="cov0" title="0">{
505 return Secret{}, errors.New("Token check failed")
508 <span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom
510 sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
511 if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{
512 return Secret{}, errors.New("Unable to read Secret at provided path")
515 // sec and err are nil in the case where a path does not exist
516 <span class="cov5" title="7">if sec == nil </span><span class="cov0" title="0">{
517 smslogger.WriteWarn("Vault read was empty. Invalid Path")
518 return Secret{}, errors.New("Secret not found at the provided path")
521 <span class="cov5" title="7">return Secret{Name: name, Values: sec.Data}, nil</span>
524 // ListSecret returns a list of secret names on a particular domain
525 // The values of the secret are not returned
526 func (v *Vault) ListSecret(dom string) ([]string, error) <span class="cov3" title="3">{
528 err := v.checkToken()
529 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
530 return nil, errors.New("Token check failed")
533 <span class="cov3" title="3">dom = v.vaultMountPrefix + "/" + dom
535 sec, err := v.vaultClient.Logical().List(dom)
536 if smslogger.CheckError(err, "Read Secret") != nil </span><span class="cov0" title="0">{
537 return nil, errors.New("Unable to read Secret at provided path")
540 // sec and err are nil in the case where a path does not exist
541 <span class="cov3" title="3">if sec == nil </span><span class="cov0" title="0">{
542 smslogger.WriteWarn("Vaultclient returned empty data")
543 return nil, errors.New("Secret not found at the provided path")
546 <span class="cov3" title="3">val, ok := sec.Data["keys"].([]interface{})
547 if !ok </span><span class="cov0" title="0">{
548 smslogger.WriteError("Secret not found at the provided path")
549 return nil, errors.New("Secret not found at the provided path")
552 <span class="cov3" title="3">retval := make([]string, len(val))
553 for i, v := range val </span><span class="cov5" title="7">{
554 retval[i] = fmt.Sprint(v)
557 <span class="cov3" title="3">return retval, nil</span>
560 // Mounts the internal Domain if its not already mounted
561 func (v *Vault) mountInternalDomain(name string) error <span class="cov5" title="8">{
563 if v.internalDomainMounted </span><span class="cov1" title="1">{
567 <span class="cov5" title="7">name = strings.TrimSpace(name)
568 mountPath := v.vaultMountPrefix + "/" + name
569 mountInput := &vaultapi.MountInput{
571 Description: "Mount point for domain: " + name,
574 Config: vaultapi.MountConfigInput{},
577 err := v.vaultClient.Sys().Mount(mountPath, mountInput)
578 if smslogger.CheckError(err, "Mount internal Domain") != nil </span><span class="cov1" title="1">{
579 if strings.Contains(err.Error(), "existing mount") </span><span class="cov1" title="1">{
580 // It is already mounted
581 v.internalDomainMounted = true
584 // Ran into some other error mounting it.
585 <span class="cov0" title="0">return errors.New("Unable to mount internal Domain")</span>
588 <span class="cov5" title="6">v.internalDomainMounted = true
592 // Stores the UUID created for secretdomain in vault
593 // under v.vaultMountPrefix / smsinternal domain
594 func (v *Vault) storeUUID(uuid string, name string) error <span class="cov5" title="8">{
596 // Check if token is still valid
597 err := v.checkToken()
598 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
599 return errors.New("Token Check failed")
602 <span class="cov5" title="8">err = v.mountInternalDomain(v.internalDomain)
603 if smslogger.CheckError(err, "Mount Internal Domain") != nil </span><span class="cov0" title="0">{
607 <span class="cov5" title="8">secret := Secret{
609 Values: map[string]interface{}{
614 err = v.CreateSecret(v.internalDomain, secret)
615 if smslogger.CheckError(err, "Write UUID to domain") != nil </span><span class="cov0" title="0">{
619 <span class="cov5" title="8">return nil</span>
622 // CreateSecretDomain mounts the kv backend on a path with the given name
623 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) <span class="cov5" title="8">{
625 // Check if token is still valid
626 err := v.checkToken()
627 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
628 return SecretDomain{}, errors.New("Token Check failed")
631 <span class="cov5" title="8">name = strings.TrimSpace(name)
632 mountPath := v.vaultMountPrefix + "/" + name
633 mountInput := &vaultapi.MountInput{
635 Description: "Mount point for domain: " + name,
638 Config: vaultapi.MountConfigInput{},
641 err = v.vaultClient.Sys().Mount(mountPath, mountInput)
642 if smslogger.CheckError(err, "Create Domain") != nil </span><span class="cov0" title="0">{
643 return SecretDomain{}, errors.New("Unable to create Secret Domain")
646 <span class="cov5" title="8">uuid, _ := uuid.GenerateUUID()
647 err = v.storeUUID(uuid, name)
648 if smslogger.CheckError(err, "Store UUID") != nil </span><span class="cov0" title="0">{
649 // Mount was successful at this point.
650 // Rollback the mount operation since we could not
651 // store the UUID for the mount.
652 v.vaultClient.Sys().Unmount(mountPath)
653 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
656 <span class="cov5" title="8">return SecretDomain{uuid, name}, nil</span>
659 // CreateSecret creates a secret mounted on a particular domain name
660 // The secret itself is mounted on a path specified by name
661 func (v *Vault) CreateSecret(dom string, sec Secret) error <span class="cov7" title="18">{
663 err := v.checkToken()
664 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
665 return errors.New("Token check failed")
668 <span class="cov7" title="18">dom = v.vaultMountPrefix + "/" + dom
670 // Vault return is empty on successful write
671 // TODO: Check if values is not empty
672 _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
673 if smslogger.CheckError(err, "Create Secret") != nil </span><span class="cov0" title="0">{
674 return errors.New("Unable to create Secret at provided path")
677 <span class="cov7" title="18">return nil</span>
680 // DeleteSecretDomain deletes a secret domain which translates to
681 // an unmount operation on the given path in Vault
682 func (v *Vault) DeleteSecretDomain(name string) error <span class="cov3" title="3">{
684 err := v.checkToken()
685 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
686 return errors.New("Token Check Failed")
689 <span class="cov3" title="3">name = strings.TrimSpace(name)
690 mountPath := v.vaultMountPrefix + "/" + name
692 err = v.vaultClient.Sys().Unmount(mountPath)
693 if smslogger.CheckError(err, "Delete Domain") != nil </span><span class="cov0" title="0">{
694 return errors.New("Unable to delete domain specified")
697 <span class="cov3" title="3">return nil</span>
700 // DeleteSecret deletes a secret mounted on the path provided
701 func (v *Vault) DeleteSecret(dom string, name string) error <span class="cov5" title="7">{
703 err := v.checkToken()
704 if smslogger.CheckError(err, "Token Check") != nil </span><span class="cov0" title="0">{
705 return errors.New("Token check failed")
708 <span class="cov5" title="7">dom = v.vaultMountPrefix + "/" + dom
710 // Vault return is empty on successful delete
711 _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
712 if smslogger.CheckError(err, "Delete Secret") != nil </span><span class="cov0" title="0">{
713 return errors.New("Unable to delete Secret at provided path")
716 <span class="cov5" title="7">return nil</span>
719 // initRole is called only once during SMS bring up
720 // It initially creates a role and secret id associated with
721 // that role. Later restarts will use the existing role-id
722 // and secret-id stored on disk
723 func (v *Vault) initRole() error <span class="cov10" title="56">{
725 if v.initRoleDone </span><span class="cov9" title="48">{
729 // Use the root token once here
730 <span class="cov5" title="8">v.vaultClient.SetToken(v.vaultToken)
731 defer v.vaultClient.ClearToken()
733 // Check if roleID and secretID has already been created
734 rID, error := smsauth.ReadFromFile("auth/role")
735 if error != nil </span><span class="cov5" title="7">{
736 smslogger.WriteWarn("Unable to find RoleID. Generating...")
737 }</span><span class="cov1" title="1"> else {
738 sID, error := smsauth.ReadFromFile("auth/secret")
739 if error != nil </span><span class="cov0" title="0">{
740 smslogger.WriteWarn("Unable to find secretID. Generating...")
741 }</span><span class="cov1" title="1"> else {
744 v.initRoleDone = true
749 <span class="cov5" title="7">rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
750 path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
751 err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
752 if smslogger.CheckError(err, "Creating Policy") != nil </span><span class="cov0" title="0">{
753 return errors.New("Unable to create policy for approle creation")
756 //Check if applrole is mounted
757 <span class="cov5" title="7">authMounts, err := v.vaultClient.Sys().ListAuth()
758 if smslogger.CheckError(err, "Mount Auth Backend") != nil </span><span class="cov0" title="0">{
759 return errors.New("Unable to get mounted auth backends")
762 <span class="cov5" title="7">approleMounted := false
763 for k, v := range authMounts </span><span class="cov5" title="7">{
764 if v.Type == "approle" && k == "approle/" </span><span class="cov0" title="0">{
765 approleMounted = true
770 // Mount approle in case its not already mounted
771 <span class="cov5" title="7">if !approleMounted </span><span class="cov5" title="7">{
772 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
775 <span class="cov5" title="7">rName := v.vaultMountPrefix + "-role"
776 data := map[string]interface{}{
778 "policies": [2]string{"default", v.policyName},
782 v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
783 sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
784 if smslogger.CheckError(err, "Create RoleID") != nil </span><span class="cov0" title="0">{
785 return errors.New("Unable to create role ID for approle")
787 <span class="cov5" title="7">v.roleID = sec.Data["role_id"].(string)
789 // Create a secret-id to go with it
790 sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
791 map[string]interface{}{})
792 if smslogger.CheckError(err, "Create SecretID") != nil </span><span class="cov0" title="0">{
793 return errors.New("Unable to create secret ID for role")
796 <span class="cov5" title="7">v.secretID = sec.Data["secret_id"].(string)
797 v.initRoleDone = true
799 * Revoke the Root token.
800 * If a new Root Token is needed, it will need to be created
801 * using the unseal shards.
803 err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
804 if smslogger.CheckError(err, "Revoke Root Token") != nil </span><span class="cov0" title="0">{
805 smslogger.WriteWarn("Unable to Revoke Token")
806 }</span><span class="cov5" title="7"> else {
807 // Revoked successfully and clear it
811 // Store the role-id and secret-id
812 // We will need this if SMS restarts
813 <span class="cov5" title="7">smsauth.WriteToFile(v.roleID, "auth/role")
814 smsauth.WriteToFile(v.secretID, "auth/secret")
819 // Function checkToken() gets called multiple times to create
821 func (v *Vault) checkToken() error <span class="cov9" title="54">{
826 // Init Role if it is not yet done
827 // Role needs to be created before token can be created
829 if err != nil </span><span class="cov0" title="0">{
830 smslogger.WriteError(err.Error())
831 return errors.New("Unable to initRole in checkToken")
834 // Return immediately if token still has life
835 <span class="cov9" title="54">if v.vaultClient.Token() != "" &&
836 time.Since(v.vaultTempTokenTTL) < time.Minute*50 </span><span class="cov9" title="47">{
840 // Create a temporary token using our roleID and secretID
841 <span class="cov5" title="7">out, err := v.vaultClient.Logical().Write("auth/approle/login",
842 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
843 if smslogger.CheckError(err, "Create Temp Token") != nil </span><span class="cov0" title="0">{
844 return errors.New("Unable to create Temporary Token for Role")
847 <span class="cov5" title="7">tok, err := out.TokenID()
849 v.vaultTempTokenTTL = time.Now()
850 v.vaultClient.SetToken(tok)
854 // vaultInit() is used to initialize the vault in cases where it is not
855 // initialized. This happens once during intial bring up.
856 func (v *Vault) initializeVault() error <span class="cov2" title="2">{
858 // Check for vault init status and don't exit till it is initialized
859 for </span><span class="cov2" title="2">{
860 init, err := v.vaultClient.Sys().InitStatus()
861 if smslogger.CheckError(err, "Get Vault Init Status") != nil </span><span class="cov0" title="0">{
862 smslogger.WriteInfo("Trying again in 10s...")
863 time.Sleep(time.Second * 10)
866 // Did not get any error
867 <span class="cov2" title="2">if init == true </span><span class="cov1" title="1">{
868 smslogger.WriteInfo("Vault is already Initialized")
872 // init status is false
873 // break out of loop and finish initialization
874 <span class="cov1" title="1">smslogger.WriteInfo("Vault is not initialized. Initializing...")
878 // Hardcoded this to 3. We should make this configurable
880 <span class="cov1" title="1">initReq := &vaultapi.InitRequest{
885 pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
887 if smslogger.CheckError(err, "Generating PGP Keys") != nil </span><span class="cov0" title="0">{
888 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
889 }</span><span class="cov1" title="1"> else {
890 initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
891 initReq.RootTokenPGPKey = pbkey
894 <span class="cov1" title="1">resp, err := v.vaultClient.Sys().Init(initReq)
895 if smslogger.CheckError(err, "Initialize Vault") != nil </span><span class="cov0" title="0">{
896 return errors.New("FATAL: Unable to initialize Vault")
899 <span class="cov1" title="1">if resp != nil </span><span class="cov1" title="1">{
901 v.shards = resp.KeysB64
902 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
906 <span class="cov0" title="0">return errors.New("FATAL: Init response was empty")</span>
910 <pre class="file" id="file3" style="display: none">/*
911 * Copyright 2018 Intel Corporation, Inc
913 * Licensed under the Apache License, Version 2.0 (the "License");
914 * you may not use this file except in compliance with the License.
915 * You may obtain a copy of the License at
917 * http://www.apache.org/licenses/LICENSE-2.0
919 * Unless required by applicable law or agreed to in writing, software
920 * distributed under the License is distributed on an "AS IS" BASIS,
921 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
922 * See the License for the specific language governing permissions and
923 * limitations under the License.
934 // SMSConfiguration loads up all the values that are used to configure
935 // backend implementations
936 // TODO: Review these and see if they can be created/discovered dynamically
937 type SMSConfiguration struct {
938 CAFile string `json:"cafile"`
939 ServerCert string `json:"servercert"`
940 ServerKey string `json:"serverkey"`
942 BackendAddress string `json:"smsdbaddress"`
943 VaultToken string `json:"vaulttoken"`
944 DisableTLS bool `json:"disable_tls"`
945 BackendAddressEnvVariable string `json:"smsdburlenv"`
948 // SMSConfig is the structure that stores the configuration
949 var SMSConfig *SMSConfiguration
951 // ReadConfigFile reads the specified smsConfig file to setup some env variables
952 func ReadConfigFile(file string) (*SMSConfiguration, error) <span class="cov10" title="3">{
953 if SMSConfig == nil </span><span class="cov10" title="3">{
954 f, err := os.Open(file)
955 if err != nil </span><span class="cov1" title="1">{
958 <span class="cov6" title="2">defer f.Close()
960 // Default behaviour is to enable TLS
961 SMSConfig = &SMSConfiguration{DisableTLS: false}
962 decoder := json.NewDecoder(f)
963 err = decoder.Decode(SMSConfig)
964 if err != nil </span><span class="cov0" title="0">{
968 <span class="cov6" title="2">if SMSConfig.BackendAddress == "" && SMSConfig.BackendAddressEnvVariable != "" </span><span class="cov0" title="0">{
969 // Get the value from ENV variable
970 smslogger.WriteInfo("Using Environment Variable: " + SMSConfig.BackendAddressEnvVariable)
971 SMSConfig.BackendAddress = os.Getenv(SMSConfig.BackendAddressEnvVariable)
975 <span class="cov6" title="2">return SMSConfig, nil</span>
979 <pre class="file" id="file4" style="display: none">/*
980 * Copyright 2018 Intel Corporation, Inc
982 * Licensed under the Apache License, Version 2.0 (the "License");
983 * you may not use this file except in compliance with the License.
984 * You may obtain a copy of the License at
986 * http://www.apache.org/licenses/LICENSE-2.0
988 * Unless required by applicable law or agreed to in writing, software
989 * distributed under the License is distributed on an "AS IS" BASIS,
990 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
991 * See the License for the specific language governing permissions and
992 * limitations under the License.
999 "github.com/gorilla/mux"
1002 smsbackend "sms/backend"
1006 // handler stores two interface implementations that implement
1007 // the backend functionality
1008 type handler struct {
1009 secretBackend smsbackend.SecretBackend
1010 loginBackend smsbackend.LoginBackend
1013 // createSecretDomainHandler creates a secret domain with a name provided
1014 func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
1015 var d smsbackend.SecretDomain
1017 err := json.NewDecoder(r.Body).Decode(&d)
1018 if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
1019 http.Error(w, err.Error(), http.StatusBadRequest)
1023 <span class="cov6" title="3">dom, err := h.secretBackend.CreateSecretDomain(d.Name)
1024 if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
1025 http.Error(w, err.Error(), http.StatusInternalServerError)
1029 <span class="cov6" title="3">w.Header().Set("Content-Type", "application/json")
1030 w.WriteHeader(http.StatusCreated)
1031 err = json.NewEncoder(w).Encode(dom)
1032 if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil </span><span class="cov0" title="0">{
1033 http.Error(w, err.Error(), http.StatusInternalServerError)
1038 // deleteSecretDomainHandler deletes a secret domain with the name provided
1039 func (h handler) deleteSecretDomainHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
1041 domName := vars["domName"]
1043 err := h.secretBackend.DeleteSecretDomain(domName)
1044 if smslogger.CheckError(err, "DeleteSecretDomainHandler") != nil </span><span class="cov0" title="0">{
1045 http.Error(w, err.Error(), http.StatusInternalServerError)
1049 <span class="cov6" title="3">w.WriteHeader(http.StatusNoContent)</span>
1052 // createSecretHandler handles creation of secrets on a given domain name
1053 func (h handler) createSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
1054 // Get domain name from URL
1056 domName := vars["domName"]
1058 // Get secrets to be stored from body
1059 var b smsbackend.Secret
1060 err := json.NewDecoder(r.Body).Decode(&b)
1061 if smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{
1062 http.Error(w, err.Error(), http.StatusBadRequest)
1066 <span class="cov10" title="7">err = h.secretBackend.CreateSecret(domName, b)
1067 if smslogger.CheckError(err, "CreateSecretHandler") != nil </span><span class="cov0" title="0">{
1068 http.Error(w, err.Error(), http.StatusInternalServerError)
1072 <span class="cov10" title="7">w.WriteHeader(http.StatusCreated)</span>
1075 // getSecretHandler handles reading a secret by given domain name and secret name
1076 func (h handler) getSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
1078 domName := vars["domName"]
1079 secName := vars["secretName"]
1081 sec, err := h.secretBackend.GetSecret(domName, secName)
1082 if smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{
1083 http.Error(w, err.Error(), http.StatusInternalServerError)
1087 <span class="cov10" title="7">w.Header().Set("Content-Type", "application/json")
1088 err = json.NewEncoder(w).Encode(sec)
1089 if smslogger.CheckError(err, "GetSecretHandler") != nil </span><span class="cov0" title="0">{
1090 http.Error(w, err.Error(), http.StatusInternalServerError)
1095 // listSecretHandler handles listing all secrets under a particular domain name
1096 func (h handler) listSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov6" title="3">{
1098 domName := vars["domName"]
1100 secList, err := h.secretBackend.ListSecret(domName)
1101 if smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{
1102 http.Error(w, err.Error(), http.StatusInternalServerError)
1106 // Creating an anonymous struct to store the returned list of data
1107 <span class="cov6" title="3">var retStruct = struct {
1108 SecretNames []string `json:"secretnames"`
1113 w.Header().Set("Content-Type", "application/json")
1114 err = json.NewEncoder(w).Encode(retStruct)
1115 if smslogger.CheckError(err, "ListSecretHandler") != nil </span><span class="cov0" title="0">{
1116 http.Error(w, err.Error(), http.StatusInternalServerError)
1121 // deleteSecretHandler handles deleting a secret by given domain name and secret name
1122 func (h handler) deleteSecretHandler(w http.ResponseWriter, r *http.Request) <span class="cov10" title="7">{
1124 domName := vars["domName"]
1125 secName := vars["secretName"]
1127 err := h.secretBackend.DeleteSecret(domName, secName)
1128 if smslogger.CheckError(err, "DeleteSecretHandler") != nil </span><span class="cov0" title="0">{
1129 http.Error(w, err.Error(), http.StatusInternalServerError)
1133 <span class="cov10" title="7">w.WriteHeader(http.StatusNoContent)</span>
1136 // statusHandler returns information related to SMS and SMS backend services
1137 func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) <span class="cov7" title="4">{
1138 s, err := h.secretBackend.GetStatus()
1139 if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{
1140 http.Error(w, err.Error(), http.StatusInternalServerError)
1144 <span class="cov7" title="4">status := struct {
1145 Seal bool `json:"sealstatus"`
1150 w.Header().Set("Content-Type", "application/json")
1151 err = json.NewEncoder(w).Encode(status)
1152 if smslogger.CheckError(err, "StatusHandler") != nil </span><span class="cov0" title="0">{
1153 http.Error(w, err.Error(), http.StatusInternalServerError)
1158 // loginHandler handles login via password and username
1159 func (h handler) loginHandler(w http.ResponseWriter, r *http.Request) {<span class="cov0" title="0">
1163 // unsealHandler is a pass through that sends requests from quorum client
1165 func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) <span class="cov0" title="0">{
1166 // Get shards to be used for unseal
1167 type unsealStruct struct {
1168 UnsealShard string `json:"unsealshard"`
1171 var inp unsealStruct
1172 decoder := json.NewDecoder(r.Body)
1173 decoder.DisallowUnknownFields()
1174 err := decoder.Decode(&inp)
1175 if smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{
1176 http.Error(w, "Bad input JSON", http.StatusBadRequest)
1180 <span class="cov0" title="0">err = h.secretBackend.Unseal(inp.UnsealShard)
1181 if smslogger.CheckError(err, "UnsealHandler") != nil </span><span class="cov0" title="0">{
1182 http.Error(w, err.Error(), http.StatusInternalServerError)
1187 // registerHandler allows the quorum clients to register with SMS
1188 // with their PGP public keys that are then used by sms for backend
1190 func (h handler) registerHandler(w http.ResponseWriter, r *http.Request) <span class="cov1" title="1">{
1191 // Get shards to be used for unseal
1192 type registerStruct struct {
1193 PGPKey string `json:"pgpkey"`
1194 QuorumID string `json:"quorumid"`
1197 var inp registerStruct
1198 decoder := json.NewDecoder(r.Body)
1199 decoder.DisallowUnknownFields()
1200 err := decoder.Decode(&inp)
1201 if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
1202 http.Error(w, "Bad input JSON", http.StatusBadRequest)
1206 <span class="cov1" title="1">sh, err := h.secretBackend.RegisterQuorum(inp.PGPKey)
1207 if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
1208 http.Error(w, err.Error(), http.StatusInternalServerError)
1212 // Creating a struct for return data
1213 <span class="cov1" title="1">shStruct := struct {
1214 Shard string `json:"shard"`
1219 w.Header().Set("Content-Type", "application/json")
1220 err = json.NewEncoder(w).Encode(shStruct)
1221 if smslogger.CheckError(err, "RegisterHandler") != nil </span><span class="cov0" title="0">{
1222 http.Error(w, err.Error(), http.StatusInternalServerError)
1227 // CreateRouter returns an http.Handler for the registered URLs
1228 // Takes an interface implementation as input
1229 func CreateRouter(b smsbackend.SecretBackend) http.Handler <span class="cov4" title="2">{
1230 h := handler{secretBackend: b}
1232 // Create a new mux to handle URL endpoints
1233 router := mux.NewRouter()
1235 router.HandleFunc("/v1/sms/login", h.loginHandler).Methods("POST")
1237 // Initialization APIs which will be used by quorum client
1238 // to unseal and to provide root token to sms service
1239 router.HandleFunc("/v1/sms/quorum/status", h.statusHandler).Methods("GET")
1240 router.HandleFunc("/v1/sms/quorum/unseal", h.unsealHandler).Methods("POST")
1241 router.HandleFunc("/v1/sms/quorum/register", h.registerHandler).Methods("POST")
1243 router.HandleFunc("/v1/sms/domain", h.createSecretDomainHandler).Methods("POST")
1244 router.HandleFunc("/v1/sms/domain/{domName}", h.deleteSecretDomainHandler).Methods("DELETE")
1246 router.HandleFunc("/v1/sms/domain/{domName}/secret", h.createSecretHandler).Methods("POST")
1247 router.HandleFunc("/v1/sms/domain/{domName}/secret", h.listSecretHandler).Methods("GET")
1248 router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.getSecretHandler).Methods("GET")
1249 router.HandleFunc("/v1/sms/domain/{domName}/secret/{secretName}", h.deleteSecretHandler).Methods("DELETE")
1255 <pre class="file" id="file5" style="display: none">/*
1256 * Copyright 2018 Intel Corporation, Inc
1258 * Licensed under the Apache License, Version 2.0 (the "License");
1259 * you may not use this file except in compliance with the License.
1260 * You may obtain a copy of the License at
1262 * http://www.apache.org/licenses/LICENSE-2.0
1264 * Unless required by applicable law or agreed to in writing, software
1265 * distributed under the License is distributed on an "AS IS" BASIS,
1266 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1267 * See the License for the specific language governing permissions and
1268 * limitations under the License.
1279 var errL, warnL, infoL *log.Logger
1280 var stdErr, stdWarn, stdInfo *log.Logger
1282 // Init will be called by sms.go before any other packages use it
1283 func Init(filePath string) <span class="cov1" title="1">{
1285 stdErr = log.New(os.Stderr, "ERROR: ", log.Lshortfile|log.LstdFlags)
1286 stdWarn = log.New(os.Stdout, "WARNING: ", log.Lshortfile|log.LstdFlags)
1287 stdInfo = log.New(os.Stdout, "INFO: ", log.Lshortfile|log.LstdFlags)
1289 if filePath == "" </span><span class="cov0" title="0">{
1290 // We will just to std streams
1294 <span class="cov1" title="1">f, err := os.Create(filePath)
1295 if err != nil </span><span class="cov0" title="0">{
1296 stdErr.Println("Unable to create log file: " + err.Error())
1300 <span class="cov1" title="1">errL = log.New(f, "ERROR: ", log.Lshortfile|log.LstdFlags)
1301 warnL = log.New(f, "WARNING: ", log.Lshortfile|log.LstdFlags)
1302 infoL = log.New(f, "INFO: ", log.Lshortfile|log.LstdFlags)</span>
1305 // WriteError writes output to the writer we have
1306 // defined during its creation with ERROR prefix
1307 func WriteError(msg string) <span class="cov0" title="0">{
1308 if errL != nil </span><span class="cov0" title="0">{
1309 errL.Output(2, fmt.Sprintln(msg))
1311 <span class="cov0" title="0">if stdErr != nil </span><span class="cov0" title="0">{
1312 stdErr.Output(2, fmt.Sprintln(msg))
1316 // WriteWarn writes output to the writer we have
1317 // defined during its creation with WARNING prefix
1318 func WriteWarn(msg string) <span class="cov0" title="0">{
1319 if warnL != nil </span><span class="cov0" title="0">{
1320 warnL.Output(2, fmt.Sprintln(msg))
1322 <span class="cov0" title="0">if stdWarn != nil </span><span class="cov0" title="0">{
1323 stdWarn.Output(2, fmt.Sprintln(msg))
1327 // WriteInfo writes output to the writer we have
1328 // defined during its creation with INFO prefix
1329 func WriteInfo(msg string) <span class="cov1" title="1">{
1330 if infoL != nil </span><span class="cov1" title="1">{
1331 infoL.Output(2, fmt.Sprintln(msg))
1333 <span class="cov1" title="1">if stdInfo != nil </span><span class="cov1" title="1">{
1334 stdInfo.Output(2, fmt.Sprintln(msg))
1338 //CheckError is a helper function to reduce
1339 //repitition of error checkign blocks of code
1340 func CheckError(err error, topic string) error <span class="cov10" title="116">{
1341 if err != nil </span><span class="cov1" title="1">{
1342 msg := topic + ": " + err.Error()
1343 if errL != nil </span><span class="cov1" title="1">{
1344 errL.Output(2, fmt.Sprintln(msg))
1346 <span class="cov1" title="1">if stdErr != nil </span><span class="cov1" title="1">{
1347 stdErr.Output(2, fmt.Sprintln(msg))
1349 <span class="cov1" title="1">return err</span>
1351 <span class="cov9" title="115">return nil</span>
1355 <pre class="file" id="file6" style="display: none">/*
1356 * Copyright 2018 Intel Corporation, Inc
1358 * Licensed under the Apache License, Version 2.0 (the "License");
1359 * you may not use this file except in compliance with the License.
1360 * You may obtain a copy of the License at
1362 * http://www.apache.org/licenses/LICENSE-2.0
1364 * Unless required by applicable law or agreed to in writing, software
1365 * distributed under the License is distributed on an "AS IS" BASIS,
1366 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1367 * See the License for the specific language governing permissions and
1368 * limitations under the License.
1381 smsbackend "sms/backend"
1382 smsconfig "sms/config"
1383 smshandler "sms/handler"
1387 func main() <span class="cov8" title="1">{
1388 // Initialize logger
1389 smslogger.Init("sms.log")
1391 // Read Configuration File
1392 smsConf, err := smsconfig.ReadConfigFile("smsconfig.json")
1393 if err != nil </span><span class="cov0" title="0">{
1397 <span class="cov8" title="1">backendImpl, err := smsbackend.InitSecretBackend()
1398 if err != nil </span><span class="cov0" title="0">{
1402 <span class="cov8" title="1">httpRouter := smshandler.CreateRouter(backendImpl)
1404 httpServer := &http.Server{
1405 Handler: httpRouter,
1409 // Listener for SIGINT so that it returns cleanly
1410 connectionsClose := make(chan struct{})
1411 go func() </span><span class="cov8" title="1">{
1412 c := make(chan os.Signal, 1)
1413 signal.Notify(c, os.Interrupt)
1415 httpServer.Shutdown(context.Background())
1416 close(connectionsClose)
1419 // Start in TLS mode by default
1420 <span class="cov8" title="1">if smsConf.DisableTLS == true </span><span class="cov0" title="0">{
1421 smslogger.WriteWarn("TLS is Disabled")
1422 err = httpServer.ListenAndServe()
1423 }</span><span class="cov8" title="1"> else {
1424 // TODO: Use CA certificate from AAF
1425 tlsConfig, err := smsauth.GetTLSConfig(smsConf.CAFile)
1426 if err != nil </span><span class="cov0" title="0">{
1430 <span class="cov8" title="1">httpServer.TLSConfig = tlsConfig
1431 err = httpServer.ListenAndServeTLS(smsConf.ServerCert, smsConf.ServerKey)</span>
1434 <span class="cov8" title="1">if err != nil && err != http.ErrServerClosed </span><span class="cov0" title="0">{
1438 <span class="cov8" title="1"><-connectionsClose</span>
1446 var files = document.getElementById('files');
1448 files.addEventListener('change', onChange, false);
1449 function select(part) {
1451 visible.style.display = 'none';
1452 visible = document.getElementById(part);
1456 visible.style.display = 'block';
1457 location.hash = part;
1459 function onChange() {
1460 select(files.value);
1461 window.scrollTo(0, 0);
1463 if (location.hash != "") {
1464 select(location.hash.substr(1));