Compare commits

..

8 Commits

Author SHA1 Message Date
Ashwathi Shiva
26955d749e fixed typo 2020-05-08 16:33:30 -04:00
Ashwathi Shiva
3e678d9056 fixed typo 2020-05-08 16:32:38 -04:00
Ashwathi Shiva
ec9423330d updated docs and typo in help text 2020-05-08 16:18:26 -04:00
Ashwathi Shiva
c5b6443179 updated doc 2020-05-08 16:14:00 -04:00
Ashwathi Shiva
a228757e0b need for both priv key and pub key in the client cert 2020-05-08 15:35:11 -04:00
Ashwathi Shiva
8f2e464d0e Issue-85- replaced github-errors with golang equivalent 2020-05-08 00:34:30 -04:00
Ashwathi Shiva
9d547bfe0c mongo check working when ca cert and client cert put in same file 2020-05-08 00:09:25 -04:00
Ashwathi Shiva
7525c2e698 Preflight mongo mutual tls (#357)
* preflight mongo mutual tls working when cert is specified in CR
2020-05-04 09:42:43 -04:00
9 changed files with 154 additions and 80 deletions

View File

@@ -152,7 +152,7 @@ func pfDeploymentCheckCmd(q *qliksense.Qliksense) *cobra.Command {
}
var pfDeploymentCheckCmd = &cobra.Command{
Use: "deployment",
Short: "perform preflight deploymwnt check",
Short: "perform preflight deployment check",
Long: `perform preflight deployment check to ensure that we can create deployments in the cluster`,
Example: `qliksense preflight deployment`,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -337,7 +337,7 @@ func pfCreateServiceAccountCheckCmd(q *qliksense.Qliksense) *cobra.Command {
var preflightServiceAccountCmd = &cobra.Command{
Use: "serviceaccount",
Short: "preflight create ServiceAccount check",
Short: "preflight create serviceaccount check",
Long: `perform preflight serviceaccount check to ensure we are able to create a service account in the cluster`,
Example: `qliksense preflight serviceaccount`,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -480,42 +480,3 @@ func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}
func pfStorageClassCheckCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfStorageClassCmd = &cobra.Command{
Use: "storageclass",
Short: "perform preflight storage class check",
Long: `perform preflight storage class check to ensure that we are able to create a storage class in the cluster`,
Example: `qliksense preflight storageclass`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts}
// Preflight storage class check
namespace, kubeConfigContents, err := preflight.InitPreflight()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight storageclass check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.CheckStorageClass(namespace, kubeConfigContents, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("Preflight storageclass check FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("Preflight storageclass check PASSED"))
return nil
},
}
f := pfStorageClassCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfStorageClassCmd
}

View File

@@ -46,7 +46,7 @@ func initAndExecute() error {
log.Fatal(err)
}
// create dirs and appropriate files for setting up contexts
api.LogDebugMessage("QliksenseHomeDir: %s", qlikSenseHome)
api.LogDebugMessage("QliksenseHomeDir: %s\n", qlikSenseHome)
qliksenseClient := qliksense.New(qlikSenseHome)
cmd := rootCmd(qliksenseClient)
@@ -219,7 +219,6 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfStorageClassCheckCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)

View File

@@ -16,9 +16,18 @@ Examples:
qliksense preflight <preflight_check_to_run>
Available Commands:
all perform all checks
dns perform preflight dns check
k8s-version check k8s version
all perform all checks
authcheck preflight authcheck
clean perform preflight clean
deployment perform preflight deployment check
dns perform preflight dns check
kube-version check kubernetes version
mongo preflight mongo OR preflight mongo --url=<url>
pod perform preflight pod check
role preflight create role check
rolebinding preflight create rolebinding check
service perform preflight service check
serviceaccount preflight create ServiceAccount check
Flags:
-h, --help help for preflight
@@ -200,8 +209,8 @@ We can check if we are able to connect to an instance of mongodb on the cluster
qliksense preflight mongo --url=<url> -v OR
qliksense preflight mongo -v
qliksense preflight mongo --url=<mongo-server url> --ca-cert=<path to ca-cert file> -v
```
```shell
Preflight mongo check
---------------------
Preflight mongodb check:
@@ -217,7 +226,29 @@ Deleted pod: pf-mongo-pod
Completed preflight mongodb check
```
#### Mongodb check with mutual tls
In order to perform mutual tls with mongo we need to:
- append client certificate to the beginning/end of CA certificate. Make sure to include the beginning and end tags on each certificate.
The CA certificate file should look like this in the end:
```shell
<existing contents of CA cert>
...
-----BEGIN RSA PRIVATE KEY-----
<private key>
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
<public key>
-----END CERTIFICATE-----
```
- Run the command below to set the ca certificate into the CR
```shell
cat <path_to_ca.crt> | base64 | qliksense config set-secrets qliksense.caCertificates --base64
```
Next, run:
```shell
qliksense preflight mongo -v
```
### Running all checks
Run the command shown below to execute all preflight checks.

View File

@@ -96,7 +96,7 @@ func WriteToFile(content interface{}, targetFile string) error {
log.Println(err)
return err
}
LogDebugMessage("Wrote content into %s", targetFile)
LogDebugMessage("Wrote content into %s\n", targetFile)
return nil
}

View File

@@ -23,7 +23,7 @@ func checkExists(filename string) os.FileInfo {
if os.IsNotExist(err) {
return nil
}
LogDebugMessage("File exists")
LogDebugMessage("File exists\n")
return info
}
@@ -73,7 +73,7 @@ func ProcessConfigArgs(args []string, base64Encoded bool) ([]*ServiceKeyValue, e
resultSvcKV := make([]*ServiceKeyValue, len(args))
// qliksense.mongodb=somethig
for i, arg := range args {
LogDebugMessage("Arg received: %s", arg)
LogDebugMessage("Arg received: %s\n", arg)
first := strings.SplitN(arg, "=", 2)
if len(first) != 2 {
return nil, notValidErr

View File

@@ -1,8 +1,11 @@
package preflight
import (
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/semver/v3"
@@ -40,13 +43,67 @@ func (qp *QliksensePreflight) CheckMongo(kubeConfigContents []byte, namespace st
qp.P.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
return err
}
preflightOpts.MongoOptions.MongodbUrl = decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri")
preflightOpts.MongoOptions.MongodbUrl = strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbUri"))
tmpDir := os.TempDir()
caCrtFile := filepath.Join(tmpDir, "rootCA.crt")
clientCrtFile := filepath.Join(tmpDir, "mongoClient.crt")
caCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates")
clientCertStr := decryptedCR.Spec.GetFromSecrets("qliksense", "mongoDbClientCrt")
if preflightOpts.MongoOptions.CaCertFile == "" && caCertStr != "" {
api.LogDebugMessage("received ca crt: %s\n", caCertStr)
if err := ioutil.WriteFile(caCrtFile, []byte(caCertStr), 0644); err != nil {
return fmt.Errorf("unable to write CA crt to file: %v", err)
}
preflightOpts.MongoOptions.CaCertFile = caCrtFile
}
if preflightOpts.MongoOptions.ClientCertFile == "" && clientCertStr != "" {
api.LogDebugMessage("received client crt: %s\n", clientCertStr)
if err := ioutil.WriteFile(clientCrtFile, []byte(clientCertStr), 0644); err != nil {
return fmt.Errorf("unable to write client crt to file: %v", err)
}
preflightOpts.MongoOptions.ClientCertFile = clientCrtFile
}
}
if !cleanup {
qp.P.LogVerboseMessage("MongodbUrl: %s\n", preflightOpts.MongoOptions.MongodbUrl)
// if mongoDbUrl is empty, abort check
if preflightOpts.MongoOptions.MongodbUrl == "" {
qp.P.LogVerboseMessage("Mongodb Url is empty, hence aborting preflight check\n")
return errors.New("MongoDbUrl is empty")
}
}
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err != nil {
return err
var privKeys []string
var err error
if preflightOpts.MongoOptions.CaCertFile != "" && preflightOpts.MongoOptions.ClientCertFile == "" {
privKeys, err = qp.extractPrivateKeysFromCA(preflightOpts.MongoOptions.CaCertFile)
if err != nil {
return fmt.Errorf("unable to parse CA cert: %v", err)
}
}
privKeyCount := len(privKeys)
if privKeyCount == 0 {
api.LogDebugMessage("no private keys extrated from CA, hence proceeding with usual flow\n")
if err := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err != nil {
return err
}
} else {
api.LogDebugMessage("found %d private keys\n", privKeyCount)
successCount := 0
for _, privKey := range privKeys {
preflightOpts.MongoOptions.ClientCertFile = privKey
if err1 := qp.mongoConnCheck(kubeConfigContents, namespace, preflightOpts, cleanup); err1 != nil {
err = err1
continue
}
successCount++
break
}
if successCount == 0 {
return err
}
}
if !cleanup {
qp.P.LogVerboseMessage("Completed preflight mongodb check\n")
@@ -101,7 +158,7 @@ func (qp *QliksensePreflight) mongoConnCheck(kubeConfigContents []byte, namespac
mongoCommand.WriteString(fmt.Sprintf(" --password %s", preflightOpts.MongoOptions.Password))
api.LogDebugMessage("Adding username and password\n")
}
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.CaCertFile != "" || preflightOpts.MongoOptions.ClientCertFile != "" {
if preflightOpts.MongoOptions.Tls || preflightOpts.MongoOptions.ClientCertFile != "" {
mongoCommand.WriteString(" --tls")
api.LogDebugMessage("Adding --tls: Mongo command: %s\n", mongoCommand.String())
}
@@ -168,7 +225,6 @@ func (qp *QliksensePreflight) checkMongoVersion(logStr string) (bool, error) {
mongoVersionStrToCheck := "MongoDB server version:"
if strings.Contains(logStr, mongoVersionStrToCheck) {
logLines := strings.Split(logStr, "\n")
for _, eachline := range logLines {
if strings.Contains(eachline, mongoVersionStrToCheck) {
mongoVersionLog := strings.Split(eachline, ":")
@@ -208,7 +264,7 @@ func (qp *QliksensePreflight) createSecret(clientset *kubernetes.Clientset, name
certSecret, err := qp.createPreflightTestSecret(clientset, namespace, certSecretName, certBytes)
if err != nil {
err = fmt.Errorf("unable to create secret with ca cert : %v\n", err)
err = fmt.Errorf("unable to create secret with cert : %v\n", err)
return nil, err
}
return certSecret, nil
@@ -219,3 +275,41 @@ func (qp *QliksensePreflight) runMongoCleanup(clientset *kubernetes.Clientset, n
qp.deleteK8sSecret(clientset, namespace, caCertSecretName)
qp.deleteK8sSecret(clientset, namespace, clientCertSecretName)
}
func (qp *QliksensePreflight) extractPrivateKeysFromCA(cafile string) ([]string, error) {
api.LogDebugMessage("extracting private keys from CA file: %s\n", cafile)
raw, err := ioutil.ReadFile(cafile)
if err != nil {
return nil, err
}
dirPath := os.TempDir()
count := 0
var files []string
for {
block, rest := pem.Decode(raw)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
api.LogDebugMessage("found a private key\n")
privFile := filepath.Join(dirPath, fmt.Sprintf("mongo_priv_%d.key", count+1))
api.LogDebugMessage("creating a private key file: %s\n", privFile)
keyData := pem.EncodeToMemory(block)
block, rest = pem.Decode(rest)
if block != nil && block.Type == "CERTIFICATE" {
api.LogDebugMessage("block type: %s\n", block.Type)
keyData = append(keyData, pem.EncodeToMemory(block)...)
}
api.LogDebugMessage("key data: %s\n", keyData)
if err := ioutil.WriteFile(privFile, keyData, 0600); err != nil {
return nil, fmt.Errorf("error writing private key to file: \"%s\"", privFile)
}
api.LogDebugMessage("successfully wrote contents to the private key file\n")
files = append(files, privFile)
count++
}
raw = rest
}
api.LogDebugMessage("extracted private key files: %v\n", files)
return files, nil
}

View File

@@ -12,7 +12,6 @@ import (
"time"
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/qlik-oss/sense-installer/pkg/api"
"github.com/qlik-oss/sense-installer/pkg/qliksense"
appsv1 "k8s.io/api/apps/v1"
@@ -115,13 +114,13 @@ func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clients
if len(kubeconfig) == 0 {
clientConfig, err = rest.InClusterConfig()
if err != nil {
err = errors.Wrap(err, "Unable to load in-cluster kubeconfig")
err = fmt.Errorf("Unable to load in-cluster kubeconfig: %w", err)
return nil, nil, err
}
} else {
config, err := clientcmd.Load(kubeconfig)
if err != nil {
err = errors.Wrap(err, "Unable to load kubeconfig")
err = fmt.Errorf("Unable to load kubeconfig: %w", err)
return nil, nil, err
}
if contextName != "" {
@@ -129,13 +128,13 @@ func getK8SClientSet(kubeconfig []byte, contextName string) (*kubernetes.Clients
}
clientConfig, err = clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
err = errors.Wrap(err, "Unable to create client config from config")
err = fmt.Errorf("Unable to create client config from config: %w", err)
return nil, nil, err
}
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
err = errors.Wrap(err, "Unable to create clientset")
err = fmt.Errorf("Unable to create clientset: %w", err)
return nil, nil, err
}
return clientset, clientConfig, nil
@@ -186,7 +185,7 @@ func (qp *QliksensePreflight) createPreflightTestDeployment(clientset *kubernete
result, err = deploymentsClient.Create(deployment)
return err
}); err != nil {
err = errors.Wrapf(err, "unable to create deployments in the %s namespace", namespace)
err = fmt.Errorf("unable to create deployments in the %s namespace: %w", namespace, err)
return nil, err
}
qp.P.LogVerboseMessage("Created deployment %q\n", result.GetObjectMeta().GetName())
@@ -201,7 +200,7 @@ func getDeployment(clientset *kubernetes.Clientset, namespace, depName string) (
deployment, err = deploymentsClient.Get(depName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get deployments in the %s namespace", namespace)
err = fmt.Errorf("unable to get deployments in the %s namespace: %w", namespace, err)
api.LogDebugMessage("%v\n", err)
return nil, err
}
@@ -271,7 +270,7 @@ func getService(clientset *kubernetes.Clientset, namespace, svcName string) (*ap
svc, err = servicesClient.Get(svcName, v1.GetOptions{})
return err
}); err != nil {
err = errors.Wrapf(err, "unable to get services in the %s namespace", namespace)
err = fmt.Errorf("unable to get services in the %s namespace: %w", namespace, err)
return nil, err
}

View File

@@ -1,10 +0,0 @@
package preflight
import (
"fmt"
)
func (qp *QliksensePreflight) CheckStorageClass(namespace string, kubeConfigContents []byte, cleanup bool) error {
fmt.Println("Hello From Preflight storageclass check")
return nil
}

View File

@@ -18,12 +18,12 @@ import (
b64 "encoding/base64"
. "github.com/logrusorgru/aurora"
ansi "github.com/mattn/go-colorable"
"github.com/qlik-oss/sense-installer/pkg/api"
_ "gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
. "github.com/logrusorgru/aurora"
)
const (
@@ -62,7 +62,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
}
// Metadata name in qliksense CR is the name of the current context
api.LogDebugMessage("Current context: %s", qliksenseCR.GetName())
api.LogDebugMessage("Current context: %s\n", qliksenseCR.GetName())
encryptionKey, err := qConfig.GetEncryptionKeyForCurrent()
if err != nil {
return err
@@ -72,7 +72,7 @@ func (q *Qliksense) SetSecrets(args []string, isSecretSet bool, base64Encoded bo
return err
}
for _, ra := range resultArgs {
api.LogDebugMessage("value args to be encrypted: %s", ra.Value)
api.LogDebugMessage("value args to be encrypted: %s\n", ra.Value)
if err := q.processSecret(ra, encryptionKey, qliksenseCR, isSecretSet); err != nil {
return err
}
@@ -411,7 +411,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
out := ansi.NewColorableStdout()
switch args[0] {
case qliksenseConfig.Spec.CurrentContext:
fmt.Fprintln(out,Yellow("Please switch contexts to be able to delete this context."))
fmt.Fprintln(out, Yellow("Please switch contexts to be able to delete this context."))
err := fmt.Errorf(Red("Cannot delete current context - %s").String(), White(Bold(qliksenseConfig.Spec.CurrentContext)))
return err
case DefaultQliksenseContext:
@@ -452,7 +452,7 @@ func (q *Qliksense) DeleteContextConfig(args []string, flag bool) error {
if ans == true {
api.WriteToFile(&qliksenseConfig, qliksenseConfigFile)
fmt.Fprintln(out, Yellow(Underline("Warning: Active resources may still be running in-cluster")))
fmt.Fprintln(out, Green("Successfully deleted context: "),Bold(args[0]))
fmt.Fprintln(out, Green("Successfully deleted context: "), Bold(args[0]))
} else {
return nil
}