9db6da878ff5ce198816e4903f714df413a8f3df
[aaf/sms.git] / sms-service / src / quorumclient / quorumclient.go
1 /*
2 * Copyright 2018 TechMahindra
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15  */
16
17 package main
18
19 import (
20         "crypto/tls"
21         "crypto/x509"
22         "encoding/json"
23         uuid "github.com/hashicorp/go-uuid"
24         "io/ioutil"
25         "log"
26         "net/http"
27         "os"
28         "path/filepath"
29         smsauth "sms/auth"
30         smslogger "sms/log"
31         "strings"
32         "time"
33 )
34
35 func loadPGPKeys(prKeyPath string, pbKeyPath string) (string, string, error) {
36
37         var pbkey, prkey string
38         generated := false
39         prkey, err := smsauth.ReadFromFile(prKeyPath)
40         if smslogger.CheckError(err, "LoadPGP Private Key") != nil {
41                 smslogger.WriteInfo("No Private Key found. Generating...")
42                 pbkey, prkey, _ = smsauth.GeneratePGPKeyPair()
43                 generated = true
44         } else {
45                 pbkey, err = smsauth.ReadFromFile(pbKeyPath)
46                 if smslogger.CheckError(err, "LoadPGP Public Key") != nil {
47                         smslogger.WriteWarn("No Public Key found. Generating...")
48                         pbkey, prkey, _ = smsauth.GeneratePGPKeyPair()
49                         generated = true
50                 }
51         }
52
53         // Storing the keys to file to allow for recovery during restarts
54         if generated {
55                 smsauth.WriteToFile(prkey, prKeyPath)
56                 smsauth.WriteToFile(pbkey, pbKeyPath)
57         }
58
59         return pbkey, prkey, nil
60
61 }
62
63 //This application checks the backend status and
64 //calls necessary initialization endpoints on the
65 //SMS webservice
66 func main() {
67         folderName := filepath.Join("auth", os.Getenv("HOSTNAME"))
68         //Make sure to create the folder. It is not guaranteed to exist
69         os.MkdirAll(folderName, 0700)
70
71         idFilePath := filepath.Join(folderName, "id")
72         pbKeyPath := filepath.Join(folderName, "pbkey")
73         prKeyPath := filepath.Join(folderName, "prkey")
74         shardPath := filepath.Join(folderName, "shard")
75
76         smslogger.Init("quorum.log")
77         smslogger.WriteInfo("Starting Log for Quorum Client")
78
79         /*
80                 myID is used to uniquely identify the quorum client
81                 Using any other information such as hostname is not
82                 guaranteed to be unique.
83                 In Kubernetes, pod restarts will also change the hostname
84         */
85         myID, err := smsauth.ReadFromFile(idFilePath)
86         if smslogger.CheckError(err, "Read ID") != nil {
87                 smslogger.WriteWarn("Unable to find an ID for this client. Generating...")
88                 myID, _ = uuid.GenerateUUID()
89                 smsauth.WriteToFile(myID, idFilePath)
90         }
91
92         /*
93                 readMyShard will read the shard from disk when this client
94                 instance restarts. It will return err when a shard is not found.
95                 This is the case for first startup
96         */
97         registrationDone := true
98         myShard, err := smsauth.ReadFromFile(shardPath)
99         if smslogger.CheckError(err, "Read Shard") != nil {
100                 smslogger.WriteWarn("Unable to find a shard file. Registering with SMS...")
101                 registrationDone = false
102         }
103
104         pbkey, prkey, _ := loadPGPKeys(prKeyPath, pbKeyPath)
105
106         //Struct to read json configuration file
107         type config struct {
108                 BackEndURL string `json:"url"`
109                 CAFile     string `json:"cafile"`
110                 ClientCert string `json:"clientcert"`
111                 ClientKey  string `json:"clientkey"`
112                 TimeOut    string `json:"timeout"`
113                 DisableTLS bool   `json:"disable_tls"`
114         }
115
116         //Load the config File for reading
117         vcf, err := os.Open("config.json")
118         if err != nil {
119                 log.Fatalf("Error reading config file %v", err)
120         }
121
122         cfg := config{}
123         err = json.NewDecoder(vcf).Decode(&cfg)
124         if err != nil {
125                 log.Fatalf("Error while parsing config file %v", err)
126         }
127
128         transport := http.Transport{}
129
130         if cfg.DisableTLS == false {
131                 // Read the CA cert. This can be the self-signed CA
132                 // or CA cert provided by AAF
133                 caCert, err := ioutil.ReadFile(cfg.CAFile)
134                 if err != nil {
135                         log.Fatalf("Error while reading CA file %v ", err)
136                 }
137
138                 caCertPool := x509.NewCertPool()
139                 caCertPool.AppendCertsFromPEM(caCert)
140
141                 /*
142                         Support Client certificates once we have auto generated certs
143                         Load the client certificate files
144                         cert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
145                         if err != nil {
146                                 log.Fatalf("Error while loading key pair %v ", err)
147                         }
148                 */
149
150                 transport.TLSClientConfig = &tls.Config{
151                         MinVersion: tls.VersionTLS12,
152                         RootCAs:    caCertPool,
153                         //Enable once we have proper client certificates
154                         //Certificates: []tls.Certificate{cert},
155                 }
156         }
157
158         client := &http.Client{
159                 Transport: &transport,
160         }
161
162         duration, _ := time.ParseDuration(cfg.TimeOut)
163         ticker := time.NewTicker(duration)
164
165         for _ = range ticker.C {
166
167                 //URL and Port is configured in config file
168                 response, err := client.Get(cfg.BackEndURL + "/v1/sms/quorum/status")
169                 if smslogger.CheckError(err, "Connect to SMS") != nil {
170                         continue
171                 }
172
173                 var data struct {
174                         Seal bool `json:"sealstatus"`
175                 }
176                 err = json.NewDecoder(response.Body).Decode(&data)
177                 sealed := data.Seal
178
179                 // Unseal the vault if sealed
180                 if sealed {
181                         //Register with SMS if not already done so
182                         if !registrationDone {
183                                 body := strings.NewReader(`{"pgpkey":"` + pbkey + `","quorumid":"` + myID + `"}`)
184                                 res, err := client.Post(cfg.BackEndURL+"/v1/sms/quorum/register", "application/json", body)
185                                 if smslogger.CheckError(err, "Register with SMS") != nil {
186                                         continue
187                                 }
188                                 registrationDone = true
189                                 var data struct {
190                                         Shard string `json:"shard"`
191                                 }
192                                 json.NewDecoder(res.Body).Decode(&data)
193                                 myShard = data.Shard
194                                 smsauth.WriteToFile(myShard, shardPath)
195                         }
196
197                         decShard, err := smsauth.DecryptPGPString(myShard, prkey)
198                         body := strings.NewReader(`{"unsealshard":"` + decShard + `"}`)
199                         //URL and PORT is configured via config file
200                         response, err = client.Post(cfg.BackEndURL+"/v1/sms/quorum/unseal", "application/json", body)
201                         if smslogger.CheckError(err, "Unsealing Vault") != nil {
202                                 continue
203                         }
204                 }
205         }
206 }