/* * 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" "encoding/pem" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" "io/ioutil" smsconfig "sms/config" 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, certFile string, keyFile string) (*tls.Config, error) { // Initialize tlsConfig once caCert, err := ioutil.ReadFile(caCertFile) if smslogger.CheckError(err, "Read CA Cert file") != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) tlsConfig := &tls.Config{ // Change to RequireAndVerify once we have mandatory certs ClientAuth: tls.VerifyClientCertIfGiven, ClientCAs: caCertPool, MinVersion: tls.VersionTLS12, } certPEMBlk, err := readPEMBlock(certFile) if smslogger.CheckError(err, "Read Cert File") != nil { return nil, err } keyPEMBlk, err := readPEMBlock(keyFile) if smslogger.CheckError(err, "Read Key File") != nil { return nil, err } tlsConfig.Certificates = make([]tls.Certificate, 1) tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlk, keyPEMBlk) if smslogger.CheckError(err, "Load x509 cert and key") != nil { return nil, err } tlsConfig.BuildNameToCertificate() return tlsConfig, nil } func readPEMBlock(filename string) ([]byte, error) { pemData, err := ioutil.ReadFile(filename) if smslogger.CheckError(err, "Read PEM File") != nil { return nil, err } pemBlock, rest := pem.Decode(pemData) if len(rest) > 0 { smslogger.WriteWarn("Pemfile has extra data") } if x509.IsEncryptedPEMBlock(pemBlock) { pByte, err := base64.StdEncoding.DecodeString(smsconfig.SMSConfig.Password) if smslogger.CheckError(err, "Decode PEM Password") != nil { return nil, err } pemData, err = x509.DecryptPEMBlock(pemBlock, pByte) if smslogger.CheckError(err, "Decrypt PEM Data") != nil { return nil, err } var newPEMBlock pem.Block newPEMBlock.Type = pemBlock.Type newPEMBlock.Bytes = pemData // Converting back to PEM from DER data you get from // DecryptPEMBlock pemData = pem.EncodeToMemory(&newPEMBlock) } return pemData, nil } // 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) { var entity *openpgp.Entity config := &packet.Config{ DefaultHash: crypto.SHA256, } entity, err := openpgp.NewEntity("aaf.sms.init", "PGP Key for unsealing", "", config) if smslogger.CheckError(err, "Create Entity") != nil { return "", "", err } // Sign the identity in the entity for _, id := range entity.Identities { err = id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil) if smslogger.CheckError(err, "Sign Entity") != nil { return "", "", err } } // Sign the subkey in the entity for _, subkey := range entity.Subkeys { err := subkey.Sig.SignKey(subkey.PublicKey, entity.PrivateKey, nil) if smslogger.CheckError(err, "Sign Subkey") != nil { return "", "", err } } 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 } // EncryptPGPString takes data and a public key and encrypts using that // public key func EncryptPGPString(data string, pbKey string) (string, error) { pbKeyBytes, err := base64.StdEncoding.DecodeString(pbKey) if smslogger.CheckError(err, "Decoding Base64 Public Key") != nil { return "", err } dataBytes := []byte(data) pbEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(pbKeyBytes))) if smslogger.CheckError(err, "Reading entity from PGP key") != nil { return "", err } // encrypt string buf := new(bytes.Buffer) out, err := openpgp.Encrypt(buf, []*openpgp.Entity{pbEntity}, nil, nil, nil) if smslogger.CheckError(err, "Creating Encryption Pipe") != nil { return "", err } _, err = out.Write(dataBytes) if smslogger.CheckError(err, "Writing to Encryption Pipe") != nil { return "", err } err = out.Close() if smslogger.CheckError(err, "Closing Encryption Pipe") != nil { return "", err } crp := base64.StdEncoding.EncodeToString(buf.Bytes()) return crp, nil } // DecryptPGPString decrypts a PGP encoded input string and returns // a base64 representation of the decoded string func DecryptPGPString(data string, prKey string) (string, error) { // Convert private key to bytes from base64 prKeyBytes, err := base64.StdEncoding.DecodeString(prKey) if smslogger.CheckError(err, "Decoding Base64 Private Key") != nil { return "", err } dataBytes, err := base64.StdEncoding.DecodeString(data) if smslogger.CheckError(err, "Decoding base64 data") != nil { return "", err } prEntity, err := openpgp.ReadEntity(packet.NewReader(bytes.NewBuffer(prKeyBytes))) if smslogger.CheckError(err, "Read Entity") != nil { return "", err } prEntityList := &openpgp.EntityList{prEntity} message, err := openpgp.ReadMessage(bytes.NewBuffer(dataBytes), prEntityList, nil, nil) if smslogger.CheckError(err, "Decrypting Message") != nil { return "", err } var retBuf bytes.Buffer retBuf.ReadFrom(message.UnverifiedBody) return retBuf.String(), nil } // ReadFromFile reads a file and loads the PGP key into // a string func ReadFromFile(fileName string) (string, error) { data, err := ioutil.ReadFile(fileName) if smslogger.CheckError(err, "Read from file") != nil { return "", err } return string(data), nil } // WriteToFile writes a PGP key into a file. // It will truncate the file if it exists func WriteToFile(data string, fileName string) error { err := ioutil.WriteFile(fileName, []byte(data), 0600) if smslogger.CheckError(err, "Write to file") != nil { return err } return nil }
/* * 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) { backendImpl := &Vault{ vaultAddress: smsconfig.SMSConfig.BackendAddress, vaultToken: smsconfig.SMSConfig.VaultToken, } err := backendImpl.Init() if smslogger.CheckError(err, "InitSecretBackend") != nil { return nil, err } return backendImpl, nil } // LoginBackend Interface that will be implemented for various login backends type LoginBackend interface { }
/* * 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 { vaultCFG := vaultapi.DefaultConfig() vaultCFG.Address = v.vaultAddress client, err := vaultapi.NewClient(vaultCFG) if smslogger.CheckError(err, "Create new vault client") != nil { return err } v.initRoleDone = false v.policyName = "smsvaultpolicy" v.vaultClient = client v.vaultMountPrefix = "sms" v.internalDomain = "smsinternaldomain" v.internalDomainMounted = false v.prkey = "" return nil } // 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 { 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 { smslogger.WriteInfo("InitRole will try again later") } return nil } // GetStatus returns the current seal status of vault func (v *Vault) GetStatus() (bool, error) { sys := v.vaultClient.Sys() sealStatus, err := sys.SealStatus() if smslogger.CheckError(err, "Getting Status") != nil { return false, errors.New("Error getting status") } return sealStatus.Sealed, nil } // 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) { v.Lock() defer v.Unlock() if v.shards == nil { smslogger.WriteError("Invalid operation in RegisterQuorum") return "", errors.New("Invalid operation") } // Pop the slice var sh string sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1] if len(v.shards) == 0 { v.shards = nil } // Decrypt with SMS pgp Key sh, _ = smsauth.DecryptPGPString(sh, v.prkey) // Encrypt with Quorum client pgp key sh, _ = smsauth.EncryptPGPString(sh, pgpkey) return sh, nil } // Unseal is a passthrough API that allows any // unseal or initialization processes for the backend func (v *Vault) Unseal(shard string) error { sys := v.vaultClient.Sys() _, err := sys.Unseal(shard) if smslogger.CheckError(err, "Unseal Operation") != nil { return errors.New("Unable to execute unseal operation with specified shard") } return nil } // 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) { err := v.checkToken() if smslogger.CheckError(err, "Tocken Check") != nil { return Secret{}, errors.New("Token check failed") } dom = strings.TrimSpace(dom) dom = v.vaultMountPrefix + "/" + dom sec, err := v.vaultClient.Logical().Read(dom + "/" + name) if smslogger.CheckError(err, "Read Secret") != nil { return Secret{}, errors.New("Unable to read Secret at provided path") } // sec and err are nil in the case where a path does not exist if sec == nil { smslogger.WriteWarn("Vault read was empty. Invalid Path") return Secret{}, errors.New("Secret not found at the provided path") } return Secret{Name: name, Values: sec.Data}, nil } // 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) { err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return nil, errors.New("Token check failed") } dom = strings.TrimSpace(dom) dom = v.vaultMountPrefix + "/" + dom sec, err := v.vaultClient.Logical().List(dom) if smslogger.CheckError(err, "Read Secret") != nil { return nil, errors.New("Unable to read Secret at provided path") } // sec and err are nil in the case where a path does not exist if sec == nil { smslogger.WriteWarn("Vaultclient returned empty data") return nil, errors.New("Secret not found at the provided path") } val, ok := sec.Data["keys"].([]interface{}) if !ok { smslogger.WriteError("Secret not found at the provided path") return nil, errors.New("Secret not found at the provided path") } retval := make([]string, len(val)) for i, v := range val { retval[i] = fmt.Sprint(v) } return retval, nil } // Mounts the internal Domain if its not already mounted func (v *Vault) mountInternalDomain(name string) error { if v.internalDomainMounted { return nil } name = strings.TrimSpace(name) mountPath := v.vaultMountPrefix + "/" + name mountInput := &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 { if strings.Contains(err.Error(), "existing mount") { // It is already mounted v.internalDomainMounted = true return nil } // Ran into some other error mounting it. return errors.New("Unable to mount internal Domain") } v.internalDomainMounted = true return nil } // Stores the UUID created for secretdomain in vault // under v.vaultMountPrefix / smsinternal domain func (v *Vault) storeUUID(uuid string, name string) error { // Check if token is still valid err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return errors.New("Token Check failed") } err = v.mountInternalDomain(v.internalDomain) if smslogger.CheckError(err, "Mount Internal Domain") != nil { return err } 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 { return err } return nil } // CreateSecretDomain mounts the kv backend on a path with the given name func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) { // Check if token is still valid err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return SecretDomain{}, errors.New("Token Check failed") } name = strings.TrimSpace(name) mountPath := v.vaultMountPrefix + "/" + name mountInput := &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 { return SecretDomain{}, errors.New("Unable to create Secret Domain") } uuid, _ := uuid.GenerateUUID() err = v.storeUUID(uuid, name) if smslogger.CheckError(err, "Store UUID") != nil { // 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") } return SecretDomain{uuid, name}, nil } // 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 { err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return errors.New("Token check failed") } dom = strings.TrimSpace(dom) 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 { return errors.New("Unable to create Secret at provided path") } return nil } // DeleteSecretDomain deletes a secret domain which translates to // an unmount operation on the given path in Vault func (v *Vault) DeleteSecretDomain(dom string) error { err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return errors.New("Token Check Failed") } dom = strings.TrimSpace(dom) mountPath := v.vaultMountPrefix + "/" + dom err = v.vaultClient.Sys().Unmount(mountPath) if smslogger.CheckError(err, "Delete Domain") != nil { return errors.New("Unable to delete domain specified") } return nil } // DeleteSecret deletes a secret mounted on the path provided func (v *Vault) DeleteSecret(dom string, name string) error { err := v.checkToken() if smslogger.CheckError(err, "Token Check") != nil { return errors.New("Token check failed") } dom = strings.TrimSpace(dom) 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 { return errors.New("Unable to delete Secret at provided path") } return nil } // 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 { if v.initRoleDone { return nil } // Use the root token once here 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 { smslogger.WriteWarn("Unable to find RoleID. Generating...") } else { sID, error := smsauth.ReadFromFile("auth/secret") if error != nil { smslogger.WriteWarn("Unable to find secretID. Generating...") } else { v.roleID = rID v.secretID = sID v.initRoleDone = true return nil } } 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 { return errors.New("Unable to create policy for approle creation") } //Check if applrole is mounted authMounts, err := v.vaultClient.Sys().ListAuth() if smslogger.CheckError(err, "Mount Auth Backend") != nil { return errors.New("Unable to get mounted auth backends") } approleMounted := false for k, v := range authMounts { if v.Type == "approle" && k == "approle/" { approleMounted = true break } } // Mount approle in case its not already mounted if !approleMounted { v.vaultClient.Sys().EnableAuth("approle", "approle", "") } 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 { return errors.New("Unable to create role ID for approle") } 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 { return errors.New("Unable to create secret ID for role") } 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 { smslogger.WriteWarn("Unable to Revoke Token") } else { // Revoked successfully and clear it v.vaultToken = "" } // Store the role-id and secret-id // We will need this if SMS restarts smsauth.WriteToFile(v.roleID, "auth/role") smsauth.WriteToFile(v.secretID, "auth/secret") return nil } // Function checkToken() gets called multiple times to create // temporary tokens func (v *Vault) checkToken() error { 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 { smslogger.WriteError(err.Error()) return errors.New("Unable to initRole in checkToken") } // Return immediately if token still has life if v.vaultClient.Token() != "" && time.Since(v.vaultTempTokenTTL) < time.Minute*50 { return nil } // Create a temporary token using our roleID and secretID 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 { return errors.New("Unable to create Temporary Token for Role") } tok, err := out.TokenID() v.vaultTempTokenTTL = time.Now() v.vaultClient.SetToken(tok) return nil } // 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 { // Check for vault init status and don't exit till it is initialized for { init, err := v.vaultClient.Sys().InitStatus() if smslogger.CheckError(err, "Get Vault Init Status") != nil { smslogger.WriteInfo("Trying again in 10s...") time.Sleep(time.Second * 10) continue } // Did not get any error if init == true { smslogger.WriteInfo("Vault is already Initialized") return nil } // init status is false // break out of loop and finish initialization smslogger.WriteInfo("Vault is not initialized. Initializing...") break } // Hardcoded this to 3. We should make this configurable // in the future initReq := &vaultapi.InitRequest{ SecretShares: 3, SecretThreshold: 3, } pbkey, prkey, err := smsauth.GeneratePGPKeyPair() if smslogger.CheckError(err, "Generating PGP Keys") != nil { smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!") } else { initReq.PGPKeys = []string{pbkey, pbkey, pbkey} initReq.RootTokenPGPKey = pbkey } resp, err := v.vaultClient.Sys().Init(initReq) if smslogger.CheckError(err, "Initialize Vault") != nil { return errors.New("FATAL: Unable to initialize Vault") } if resp != nil { v.prkey = prkey v.shards = resp.KeysB64 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey) return nil } return errors.New("FATAL: Init response was empty") }
/* * 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"` Password string `json:"password"` 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) { if SMSConfig == nil { f, err := os.Open(file) if err != nil { return nil, err } defer f.Close() // Default behaviour is to enable TLS SMSConfig = &SMSConfiguration{DisableTLS: false} decoder := json.NewDecoder(f) err = decoder.Decode(SMSConfig) if err != nil { return nil, err } if SMSConfig.BackendAddress == "" && SMSConfig.BackendAddressEnvVariable != "" { // Get the value from ENV variable smslogger.WriteInfo("Using Environment Variable: " + SMSConfig.BackendAddressEnvVariable) SMSConfig.BackendAddress = os.Getenv(SMSConfig.BackendAddressEnvVariable) } } return SMSConfig, nil }
/* * 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" uuid "github.com/hashicorp/go-uuid" 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) { var d smsbackend.SecretDomain err := json.NewDecoder(r.Body).Decode(&d) if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } dom, err := h.secretBackend.CreateSecretDomain(d.Name) if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) err = json.NewEncoder(w).Encode(dom) if smslogger.CheckError(err, "CreateSecretDomainHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // deleteSecretDomainHandler deletes a secret domain with the name provided func (h handler) deleteSecretDomainHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) domName := vars["domName"] err := h.secretBackend.DeleteSecretDomain(domName) if smslogger.CheckError(err, "DeleteSecretDomainHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // createSecretHandler handles creation of secrets on a given domain name func (h handler) createSecretHandler(w http.ResponseWriter, r *http.Request) { // 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(&b) if smslogger.CheckError(err, "CreateSecretHandler") != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } err = h.secretBackend.CreateSecret(domName, b) if smslogger.CheckError(err, "CreateSecretHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } // getSecretHandler handles reading a secret by given domain name and secret name func (h handler) getSecretHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) domName := vars["domName"] secName := vars["secretName"] sec, err := h.secretBackend.GetSecret(domName, secName) if smslogger.CheckError(err, "GetSecretHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(sec) if smslogger.CheckError(err, "GetSecretHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // listSecretHandler handles listing all secrets under a particular domain name func (h handler) listSecretHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) domName := vars["domName"] secList, err := h.secretBackend.ListSecret(domName) if smslogger.CheckError(err, "ListSecretHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Creating an anonymous struct to store the returned list of data 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // deleteSecretHandler handles deleting a secret by given domain name and secret name func (h handler) deleteSecretHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) domName := vars["domName"] secName := vars["secretName"] err := h.secretBackend.DeleteSecret(domName, secName) if smslogger.CheckError(err, "DeleteSecretHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // statusHandler returns information related to SMS and SMS backend services func (h handler) statusHandler(w http.ResponseWriter, r *http.Request) { s, err := h.secretBackend.GetStatus() if smslogger.CheckError(err, "StatusHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // loginHandler handles login via password and username func (h handler) loginHandler(w http.ResponseWriter, r *http.Request) { } // unsealHandler is a pass through that sends requests from quorum client // to the backend. func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) { // 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(&inp) if smslogger.CheckError(err, "UnsealHandler") != nil { http.Error(w, "Bad input JSON", http.StatusBadRequest) return } err = h.secretBackend.Unseal(inp.UnsealShard) if smslogger.CheckError(err, "UnsealHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // 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) { // 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(&inp) if smslogger.CheckError(err, "RegisterHandler") != nil { http.Error(w, "Bad input JSON", http.StatusBadRequest) return } sh, err := h.secretBackend.RegisterQuorum(inp.PGPKey) if smslogger.CheckError(err, "RegisterHandler") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Creating a struct for return data 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // healthCheckHandler runs a few commands on the backend and returns // OK or not depending on the status of the backend func (h handler) healthCheckHandler(w http.ResponseWriter, r *http.Request) { sealed, err := h.secretBackend.GetStatus() if smslogger.CheckError(err, "HealthCheck") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // backend is sealed if sealed == true { http.Error(w, "Secret Backend is not ready for operations", http.StatusInternalServerError) return } // backend is not sealed dname, _ := uuid.GenerateUUID() dom, err := h.secretBackend.CreateSecretDomain(dname) if smslogger.CheckError(err, "HealthCheck Create Domain") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } err = h.secretBackend.DeleteSecretDomain(dom.UUID) if smslogger.CheckError(err, "HealthCheck Delete Domain") != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } // CreateRouter returns an http.Handler for the registered URLs // Takes an interface implementation as input func CreateRouter(b smsbackend.SecretBackend) http.Handler { 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/healthcheck", h.healthCheckHandler).Methods("GET") 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 }
/* * 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) { 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 == "" { // We will just to std streams return } f, err := os.Create(filePath) if err != nil { stdErr.Println("Unable to create log file: " + err.Error()) return } 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) } // WriteError writes output to the writer we have // defined during its creation with ERROR prefix func WriteError(msg string) { if errL != nil { errL.Output(2, fmt.Sprintln(msg)) } if stdErr != nil { stdErr.Output(2, fmt.Sprintln(msg)) } } // WriteWarn writes output to the writer we have // defined during its creation with WARNING prefix func WriteWarn(msg string) { if warnL != nil { warnL.Output(2, fmt.Sprintln(msg)) } if stdWarn != nil { stdWarn.Output(2, fmt.Sprintln(msg)) } } // WriteInfo writes output to the writer we have // defined during its creation with INFO prefix func WriteInfo(msg string) { if infoL != nil { infoL.Output(2, fmt.Sprintln(msg)) } if stdInfo != nil { stdInfo.Output(2, fmt.Sprintln(msg)) } } //CheckError is a helper function to reduce //repetition of error checking blocks of code func CheckError(err error, topic string) error { if err != nil { msg := topic + ": " + err.Error() if errL != nil { errL.Output(2, fmt.Sprintln(msg)) } if stdErr != nil { stdErr.Output(2, fmt.Sprintln(msg)) } return err } return nil }
/* * 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() { // Initialize logger smslogger.Init("sms.log") // Read Configuration File smsConf, err := smsconfig.ReadConfigFile("smsconfig.json") if err != nil { log.Fatal(err) } backendImpl, err := smsbackend.InitSecretBackend() if err != nil { log.Fatal(err) } httpRouter := smshandler.CreateRouter(backendImpl) httpServer := &http.Server{ Handler: httpRouter, Addr: ":10443", } // Listener for SIGINT so that it returns cleanly connectionsClose := make(chan struct{}) go func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c httpServer.Shutdown(context.Background()) close(connectionsClose) }() // Start in TLS mode by default if smsConf.DisableTLS == true { smslogger.WriteWarn("TLS is Disabled") err = httpServer.ListenAndServe() } else { // Populate TLSConfig with the certificates and privatekey // information tlsConfig, err := smsauth.GetTLSConfig(smsConf.CAFile, smsConf.ServerCert, smsConf.ServerKey) if smslogger.CheckError(err, "Get TLS Configuration") != nil { log.Fatal(err) } httpServer.TLSConfig = tlsConfig // empty strings because tlsconfig already has this information err = httpServer.ListenAndServeTLS("", "") } if err != nil && err != http.ErrServerClosed { log.Fatal(err) } <-connectionsClose }