Using SNI for service names to support k8s
[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                 BackendServerName string `json:"servername"`
110                 CAFile            string `json:"cafile"`
111                 ClientCert        string `json:"clientcert"`
112                 ClientKey         string `json:"clientkey"`
113                 TimeOut           string `json:"timeout"`
114                 DisableTLS        bool   `json:"disable_tls"`
115         }
116
117         //Load the config File for reading
118         vcf, err := os.Open("config.json")
119         if err != nil {
120                 log.Fatalf("Error reading config file %v", err)
121         }
122
123         cfg := config{}
124         err = json.NewDecoder(vcf).Decode(&cfg)
125         if err != nil {
126                 log.Fatalf("Error while parsing config file %v", err)
127         }
128
129         transport := http.Transport{}
130
131         if cfg.DisableTLS == false {
132                 // Read the CA cert. This can be the self-signed CA
133                 // or CA cert provided by AAF
134                 caCert, err := ioutil.ReadFile(cfg.CAFile)
135                 if err != nil {
136                         log.Fatalf("Error while reading CA file %v ", err)
137                 }
138
139                 caCertPool := x509.NewCertPool()
140                 caCertPool.AppendCertsFromPEM(caCert)
141
142                 /*
143                         Support Client certificates once we have auto generated certs
144                         Load the client certificate files
145                         cert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
146                         if err != nil {
147                                 log.Fatalf("Error while loading key pair %v ", err)
148                         }
149                 */
150
151                 transport.TLSClientConfig = &tls.Config{
152                         MinVersion: tls.VersionTLS12,
153                         RootCAs:    caCertPool,
154                         //Enable once we have proper client certificates
155                         //Certificates: []tls.Certificate{cert},
156                 }
157         }
158
159         // Allow https connection in k8s where servername does not match
160         // certificate server name
161         if cfg.BackendServerName != "" {
162                 transport.TLSClientConfig.ServerName = cfg.BackendServerName
163         }
164
165         client := &http.Client{
166                 Transport: &transport,
167         }
168
169         duration, _ := time.ParseDuration(cfg.TimeOut)
170         ticker := time.NewTicker(duration)
171
172         for _ = range ticker.C {
173
174                 //URL and Port is configured in config file
175                 response, err := client.Get(cfg.BackEndURL + "/v1/sms/quorum/status")
176                 if smslogger.CheckError(err, "Connect to SMS") != nil {
177                         continue
178                 }
179
180                 var data struct {
181                         Seal bool `json:"sealstatus"`
182                 }
183                 err = json.NewDecoder(response.Body).Decode(&data)
184                 sealed := data.Seal
185
186                 // Unseal the vault if sealed
187                 if sealed {
188                         //Register with SMS if not already done so
189                         if !registrationDone {
190                                 body := strings.NewReader(`{"pgpkey":"` + pbkey + `","quorumid":"` + myID + `"}`)
191                                 res, err := client.Post(cfg.BackEndURL+"/v1/sms/quorum/register", "application/json", body)
192                                 if smslogger.CheckError(err, "Register with SMS") != nil {
193                                         continue
194                                 }
195                                 registrationDone = true
196                                 var data struct {
197                                         Shard string `json:"shard"`
198                                 }
199                                 json.NewDecoder(res.Body).Decode(&data)
200                                 myShard = data.Shard
201                                 smsauth.WriteToFile(myShard, shardPath)
202                         }
203
204                         decShard, err := smsauth.DecryptPGPString(myShard, prkey)
205                         body := strings.NewReader(`{"unsealshard":"` + decShard + `"}`)
206                         //URL and PORT is configured via config file
207                         response, err = client.Post(cfg.BackEndURL+"/v1/sms/quorum/unseal", "application/json", body)
208                         if smslogger.CheckError(err, "Unsealing Vault") != nil {
209                                 continue
210                         }
211                 }
212         }
213 }