Compare commits

..

8 Commits

Author SHA1 Message Date
Boris Kuschel
6ab9317638 Merge pull request #444 from qlik-oss/fix_crds
Fix crds
2020-09-30 10:07:41 -04:00
Boris Kuschel
5899760c16 retain old qliksense-init func
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-09-30 09:07:01 -04:00
Boris Kuschel
a63c400106 Use crds in profile, if exists
Signed-off-by: Boris Kuschel <boris.kuschel@qlik.com>
2020-09-30 09:03:56 -04:00
Ashwathi Shiva
568012edd8 Preflight openssl verify (#438)
* verify only server cert, not intermediate certs at this point
2020-06-25 16:50:51 -04:00
Ilir Bekteshi
34f702b183 Bump mkdocs deploy workflow version (#437)
* Bump mkdocs deploy workflow version

* Dummy commit (to trigger wf)
2020-06-25 09:17:12 +02:00
Ashwathi Shiva
d8cdbb30bb Preflight openssl verify (#436)
* qliksense preflight verify-ca-chain, included into all preflight checks and doc updates
2020-06-24 10:28:22 -04:00
Andriy Bulynko
616e759089 Rotate keys overhaul (#432) 2020-06-23 09:38:45 -04:00
Foysal Iqbal
c2d3f39542 Merge pull request #433 from qlik-oss/fix-editor
fix editor
2020-06-22 14:59:29 -04:00
30 changed files with 325 additions and 156 deletions

View File

@@ -16,6 +16,6 @@ jobs:
uses: actions/checkout@v1
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@1.11
uses: mhausenblas/mkdocs-deploy-gh-pages@1.12
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -19,7 +19,7 @@ func about(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "about ref",
Short: "Displays information pertaining to Qliksense on Kubernetes",
Short: "Displays information pertaining to qliksense on Kubernetes",
Long: "Gives the version of QLik Sense on Kubernetes and versions of images.",
Example: `
qliksense about 1.0.0

View File

@@ -24,7 +24,6 @@ func applyCmd(q *qliksense.Qliksense) *cobra.Command {
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)

View File

@@ -34,8 +34,8 @@ func crdsInstallCmd(q *qliksense.Qliksense) *cobra.Command {
}
c := &cobra.Command{
Use: "install",
Short: "Install CRDs for Qliksense application. Use install --all=false to exclude the operator CRD",
Long: "Install CRDs for Qliksense application. Use install --all=false to exclude the operator CRD",
Short: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
Long: "Install CRDs for qliksense application. Use install --all=false to exclude the operator CRD",
RunE: func(cmd *cobra.Command, args []string) error {
return q.InstallCrds(opts)
},

View File

@@ -28,11 +28,13 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
return err
}
} else {
if err1 := q.InstallQK8s(version, opts); err1 != nil {
return err1
if err := q.InstallQK8s(version, opts); err != nil {
return err
}
}
return AllPostflightChecks(q).Execute()
postflightChecksCmd := AllPostflightChecks(q)
postflightChecksCmd.DisableFlagParsing = true
return postflightChecksCmd.Execute()
},
}
@@ -40,7 +42,6 @@ func installCmd(q *qliksense.Qliksense) *cobra.Command {
f.StringVarP(&filePath, "file", "f", "", "Install from a CR file")
f.StringVarP(&opts.StorageClass, "storageClass", "s", "", "Storage class for qliksense")
f.StringVarP(&opts.MongodbUri, "mongodbUri", "m", "", "mongodbUri for qliksense (i.e. mongodb://qlik-default-mongodb:27017/qliksense?ssl=false)")
f.StringVarP(&opts.RotateKeys, "rotateKeys", "r", "", "Rotate JWT keys for qliksense (yes:rotate keys/ no:use exising keys from cluster/ None: use default EJSON_KEY from env")
f.BoolVar(&opts.CleanPatchFiles, cleanPatchFilesFlagName, opts.CleanPatchFiles, cleanPatchFilesFlagUsage)
f.BoolVarP(&opts.Pull, pullFlagName, pullFlagShorthand, opts.Pull, pullFlagUsage)
f.BoolVarP(&opts.Push, pushFlagName, pushFlagShorthand, opts.Push, pushFlagUsage)

31
cmd/qliksense/keys.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"github.com/qlik-oss/sense-installer/pkg/qliksense"
"github.com/spf13/cobra"
)
var keysCmd = &cobra.Command{
Use: "keys",
Short: "keys for qliksense",
}
func keysRotateCmd(q *qliksense.Qliksense) *cobra.Command {
c := &cobra.Command{
Use: "rotate",
Short: "Rotate qliksense application keys",
RunE: func(cmd *cobra.Command, args []string) error {
if err := q.InstallQK8s("", &qliksense.InstallCommandOptions{
CleanPatchFiles: true,
RotateKeys: true,
}); err != nil {
return err
} else {
postFlightChecksCmd := AllPostflightChecks(q)
postFlightChecksCmd.DisableFlagParsing = true
return postFlightChecksCmd.Execute()
}
},
}
return c
}

View File

@@ -472,3 +472,42 @@ func pfCleanupCmd(q *qliksense.Qliksense) *cobra.Command {
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfCleanCmd
}
func pfVerifyCAChainCmd(q *qliksense.Qliksense) *cobra.Command {
out := ansi.NewColorableStdout()
preflightOpts := &preflight.PreflightOptions{
MongoOptions: &preflight.MongoOptions{},
}
var pfVerifyCAChainCmd = &cobra.Command{
Use: "verify-ca-chain",
Short: "verify-ca-chain using openssl verify",
Long: `verify the CA chain using openssl verify to ensure that mongodb certificate is valid`,
Example: `qliksense preflight verify-ca-chain`,
RunE: func(cmd *cobra.Command, args []string) error {
qp := &preflight.QliksensePreflight{Q: q, P: preflightOpts, CG: &api.ClientGoUtils{Verbose: preflightOpts.Verbose}}
// Preflight service check
namespace, kubeConfigContents, err := qp.CG.LoadKubeConfigAndNamespace()
if err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
if namespace == "" {
namespace = "default"
}
if err = qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n", err)
return nil
}
fmt.Fprintf(out, "%s\n", Green("PASSED"))
return nil
},
}
f := pfVerifyCAChainCmd.Flags()
f.BoolVarP(&preflightOpts.Verbose, "verbose", "v", false, "verbose mode")
return pfVerifyCAChainCmd
}

View File

@@ -28,10 +28,10 @@ const (
cleanPatchFilesFlagUsage = "Set --clean=false to keep any prior config repo file changes on install (for debugging)"
pullFlagName = "pull"
pullFlagShorthand = "d"
pullFlagUsage = "If using private docker registry, pull (download) all required Qliksense images before install"
pullFlagUsage = "If using private docker registry, pull (download) all required qliksense images before install"
pushFlagName = "push"
pushFlagShorthand = "u"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded Qliksense images to that registry before install"
pushFlagUsage = "If using private docker registry, push (upload) all downloaded qliksense images to that registry before install"
rootCommandName = "qliksense"
)
@@ -100,7 +100,7 @@ func commandUsesContext(commandName string) bool {
func getRootCmd(p *qliksense.Qliksense) *cobra.Command {
cmd := &cobra.Command{
Use: rootCommandName,
Short: "Qliksense cli tool",
Short: "qliksense cli tool",
Long: `qliksense cli tool provides functionality to perform operations on qliksense-k8s, qliksense operator, and kubernetes cluster`,
Args: cobra.ArbitraryArgs,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
@@ -209,6 +209,7 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
preflightCmd.AddCommand(pfCreateRoleBindingCheckCmd(p))
preflightCmd.AddCommand(pfCreateServiceAccountCheckCmd(p))
preflightCmd.AddCommand(pfCreateAuthCheckCmd(p))
preflightCmd.AddCommand(pfVerifyCAChainCmd(p))
preflightCmd.AddCommand(pfCleanupCmd(p))
cmd.AddCommand(preflightCmd)
@@ -221,6 +222,10 @@ func rootCmd(p *qliksense.Qliksense) *cobra.Command {
postflightCmd.AddCommand(AllPostflightChecks(p))
cmd.AddCommand(postflightCmd)
// add keys command
cmd.AddCommand(keysCmd)
keysCmd.AddCommand(keysRotateCmd(p))
return cmd
}

View File

@@ -74,7 +74,6 @@ spec:
- name: mongodbUri
value: mongodb://qlik-test-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"
```
`qliksense apply` does everything `qliksense load` does but will install Qlik Sense into the cluster as well

View File

@@ -25,7 +25,6 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
rotateKeys: "yes"
releaseName: qlik-default
```

View File

@@ -305,3 +305,21 @@ Removing mongo check components...
Preflight cleanup complete
```
### Verify-ca-chain check
We use the command below to verify the ca certificate chain and server certificate. We run this check over mongodbUrl and discoveryUrl we inferred from idpconfigs in the CR.
```shell
$ qliksense preflight preflight verify-ca-chain -v
Preflight verify-ca-chain check...
-----------------------------------
Openssl verify mongodbUrl:
Mongodb url inferred form CR: <mongodbUrl_from_CR>
Host: <host extracted from mongodbUrl>
Openssl verify discoveryUrl:
Discovery url: <discoveryUrl_from_CR>
Host: <host extracted from discoveryUrl>
Completed preflight verify-CA-chain check
PASSED
```

2
go.mod
View File

@@ -40,7 +40,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/otiai10/copy v1.1.1
github.com/pkg/errors v0.9.1
github.com/qlik-oss/k-apis v0.1.10
github.com/qlik-oss/k-apis v0.1.16
github.com/robfig/cron/v3 v3.0.1
github.com/rogpeppe/go-internal v1.5.2 // indirect
github.com/spf13/cobra v0.0.6

4
go.sum
View File

@@ -885,8 +885,8 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/qlik-oss/k-apis v0.1.10 h1:adBXokJpE7oOr9wkPOHgpVbvuhLLKtqFdnX7V9MEyOs=
github.com/qlik-oss/k-apis v0.1.10/go.mod h1:qJVbbSYtZ+fFCojEyG9UoiCAmymm0JEtnhulr5M7HyU=
github.com/qlik-oss/k-apis v0.1.16 h1:R3gCZs4A3EHPNx4B7p1idWD+OhyaU/bAlGYBWc0ZNz4=
github.com/qlik-oss/k-apis v0.1.16/go.mod h1:AkNa/kaZHpGVs9l+pHe6nvz99Sp9WO1f9ylBES95o+I=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b h1:RDh3OZJOriy/ap1NUHVKsPG07N4DALaCzaqXFFK57T0=
github.com/qlik-oss/kustomize/api v0.3.3-0.20200612023448-4c1f2f38ea9b/go.mod h1:zh3yFgE5zFk1kreqzVyyj1eXyIxQJT53l4zSg8Wt4SA=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=

View File

@@ -1,11 +1,13 @@
site_name: Qlik Sense on Kubernetes CLI
repo_url: 'https://github.com/qlik-oss/sense-installer'
strict: true
theme:
name: "material"
palette:
primary: 'green'
accent: 'indigo'
markdown_extensions:
- toc:
permalink: true
@@ -15,6 +17,7 @@ markdown_extensions:
- pymdownx.superfences
- pymdownx.details
- pymdownx.tabbed
nav:
- Overview: index.md
- getting_started.md

View File

@@ -23,7 +23,6 @@ const (
QliksenseKind = "Qliksense"
QliksenseGroup = "qlik.com"
QliksenseDefaultProfile = "docker-desktop"
DefaultRotateKeys = "yes"
QliksenseMetadataName = "QliksenseConfigMetadata"
DefaultMongodbUri = "mongodb://qlik-default-mongodb:27017/qliksense?ssl=false"
DefaultMongodbUriKey = "mongodbUri"
@@ -38,8 +37,7 @@ func (qliksenseCR *QliksenseCR) AddCommonConfig(contextName string) {
})
qliksenseCR.SetName(contextName)
qliksenseCR.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
Profile: QliksenseDefaultProfile,
}
qliksenseCR.Spec.AddToSecrets("qliksense", DefaultMongodbUriKey, strings.Replace(DefaultMongodbUri, "qlik-default", contextName, 1), "")
}

View File

@@ -23,8 +23,7 @@ func TestAddCommonConfig(t *testing.T) {
q.SetName("myqliksense")
q.SetGroupVersionKind(gvk)
q.Spec = &config.CRSpec{
Profile: QliksenseDefaultProfile,
RotateKeys: DefaultRotateKeys,
Profile: QliksenseDefaultProfile,
Secrets: map[string]config.NameValues{
"qliksense": []config.NameValue{{
Name: DefaultMongodbUriKey,

View File

@@ -103,6 +103,16 @@ func (qp *QliksensePreflight) RunAllPreflightChecks(kubeConfigContents []byte, n
}
totalCount++
// Preflight verify ca chain check
if err := qp.VerifyCAChain(kubeConfigContents, namespace, preflightOpts, false); err != nil {
fmt.Fprintf(out, "%s\n", Red("FAILED"))
fmt.Printf("Error: %v\n\n", err)
} else {
fmt.Fprintf(out, "%s\n\n", Green("PASSED"))
checkCount++
}
totalCount++
if checkCount == totalCount {
// All preflight checks were successful
return nil

123
pkg/preflight/verify_ca.go Normal file
View File

@@ -0,0 +1,123 @@
package preflight
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net/url"
"strings"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (qp *QliksensePreflight) VerifyCAChain(kubeConfigContents []byte, namespace string, preflightOpts *PreflightOptions, cleanup bool) error {
var currentCR *qapi.QliksenseCR
var err error
qConfig := qapi.NewQConfig(qp.Q.QliksenseHome)
qConfig.SetNamespace(namespace)
fmt.Print("Preflight verify-ca-chain check... ")
qp.CG.LogVerboseMessage("\n----------------------------------- \n")
currentCR, err = qConfig.GetCurrentCR()
if err != nil {
qp.CG.LogVerboseMessage("Unable to retrieve current CR: %v\n", err)
return err
}
decryptedCR, err := qConfig.GetDecryptedCr(currentCR)
if err != nil {
qp.CG.LogVerboseMessage("An error occurred while retrieving mongodbUrl from current CR: %v\n", err)
return err
}
// infer ca certs form CR
caCertificates := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "caCertificates"))
fmt.Println("Openssl verify mongodbUrl:")
// infer mongodb url from CR
mongodbUrl := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("qliksense", "mongodbUri"))
qp.CG.LogVerboseMessage("Mongodb url inferred form CR: %s\n", mongodbUrl)
// parse out server and port from mongodb url and execute openssl verify
if err := qp.extractCertAndVerify(mongodbUrl, caCertificates); err != nil {
return err
}
fmt.Printf("\nOpenssl verify discoveryUrl:\n")
// infer idpConfigs form CR
idpConfigs := strings.TrimSpace(decryptedCR.Spec.GetFromSecrets("identity-providers", "idpConfigs"))
data := []map[string]interface{}{}
if err := json.Unmarshal([]byte(idpConfigs), &data); err != nil {
panic(err)
}
var discoveryUrl string
for _, idpData := range data {
discoveryUrl = idpData["discoveryUrl"].(string)
qp.CG.LogVerboseMessage("Discovery url: %s\n", discoveryUrl)
}
if err := qp.extractCertAndVerify(discoveryUrl, caCertificates); err != nil {
return err
}
qp.CG.LogVerboseMessage("Completed preflight verify-ca-chain check\n")
return nil
}
func (qp *QliksensePreflight) extractCertAndVerify(server string, caCertificates string) error {
u, err := url.Parse(server)
if err != nil {
return fmt.Errorf("unable to parse url: %v", err)
}
switch strings.ToLower(u.Scheme) {
case "http":
return fmt.Errorf("http url is not supported for this operation")
case "https":
if u.Port() == "" {
u.Host += ":443"
}
}
qp.CG.LogVerboseMessage("Host: %s, port: %s\n", u.Host, u.Port())
conn, err := tls.Dial("tcp", u.Host, &tls.Config{})
qp.CG.LogVerboseMessage("Host: %s\n", u.Host)
if err != nil {
return fmt.Errorf("failed to connect: " + err.Error())
}
defer conn.Close()
// Get the ConnectionState struct as that's the one which gives us x509.Certificate struct
x509Certificates := conn.ConnectionState().PeerCertificates
var serverCert *x509.Certificate
if len(x509Certificates) == 0 {
return fmt.Errorf("no server certificates retrieved from the server")
}
// we retrieve and verify the server certificate, we ignore intermediate certificates at this point.
for _, x509Cert := range x509Certificates {
if !x509Cert.IsCA {
serverCert = x509Cert
break
}
}
if serverCert == nil {
return fmt.Errorf("no valid server certificates retrieved from the server")
}
roots := x509.NewCertPool()
if ok := roots.AppendCertsFromPEM([]byte(caCertificates)); !ok {
return fmt.Errorf("failed to parse root certificate.")
}
opts := x509.VerifyOptions{
Roots: roots,
DNSName: u.Hostname(),
}
if _, err := serverCert.Verify(opts); err != nil {
return fmt.Errorf("failed to verify certificate: " + err.Error())
}
return nil
}

View File

@@ -9,6 +9,8 @@ import (
"path/filepath"
"strings"
"github.com/qlik-oss/k-apis/pkg/config"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
@@ -74,23 +76,21 @@ func (q *Qliksense) configEjson() error {
}
func (q *Qliksense) applyConfigToK8s(qcr *qapi.QliksenseCR) error {
if qcr.Spec.RotateKeys != "None" {
if err := q.configEjson(); err != nil {
return err
}
if err := q.configEjson(); err != nil {
return err
}
userHomeDir, err := homedir.Dir()
if err != nil {
fmt.Printf(`error fetching user's home directory: %v\n`, err)
return err
}
fmt.Println("Manifests root: " + qcr.Spec.GetManifestsRoot())
qcr.SetNamespace(qapi.GetKubectlNamespace())
b, _ := yaml.Marshal(qcr.KApiCr)
fmt.Printf("%v", string(b))
// os.Exit(0)
// generate patches
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionRestoreOrRotate, path.Join(userHomeDir, ".kube", "config"))
// apply generated manifests
profilePath := filepath.Join(qcr.Spec.GetManifestsRoot(), qcr.Spec.GetProfileDir())
fmt.Printf("Generating manifests for profile: %v\n", profilePath)

View File

@@ -176,53 +176,6 @@ func caseInsenstiveFieldByName(v reflect.Value, name string) reflect.Value {
return v.FieldByNameFunc(func(n string) bool { return strings.ToLower(n) == name })
}
func validateCR(key string, keySub string, value string, crSpec *api.QliksenseCR) (bool, *api.QliksenseCR) {
cr := reflect.ValueOf(crSpec.Spec)
keyValid := caseInsenstiveFieldByName(reflect.Indirect(cr), key)
if !keyValid.IsValid() {
//not in main spec
fmt.Println(key, "is an invalid key")
return false, crSpec
} else if keySub == "" {
if key == "rotatekeys" {
if _, err := validateInput(value); err != nil {
return false, crSpec
}
}
}
// checks if it is git or gitops
if keySub != "" {
if !keyValid.IsNil() {
if !caseInsenstiveFieldByName(reflect.Indirect(keyValid), keySub).IsValid() {
fmt.Println(keySub, "is an invalid key")
return false, crSpec
} else {
// verify gitops enabled and gitops schedule
switch keySub {
case "schedule":
if _, err := cron.ParseStandard(value); err != nil {
fmt.Println("Please enter string with standard cron scheduling syntax ")
return false, crSpec
}
case "enabled":
if !strings.EqualFold(value, "yes") && !strings.EqualFold(value, "no") {
fmt.Println("Please use yes or no for key enabled")
return false, crSpec
}
}
}
} else {
switch key {
case "opsrunner":
crSpec.Spec.OpsRunner = &config.OpsRunner{}
case "git":
crSpec.Spec.Git = &config.Repo{}
}
}
}
return true, crSpec
}
// SetOtherConfigs - set profile/storageclassname/git.repository/manifestRoot commands
func (q *Qliksense) SetOtherConfigs(args []string) error {
// retieve current context from config.yaml
@@ -269,19 +222,8 @@ func processSetSingleArg(arg string, cr *api.QliksenseCR) error {
cr.Spec.Profile = nv[1]
case "storageClassName":
cr.Spec.StorageClassName = nv[1]
case "rotateKeys":
valid := false
for _, v := range []string{"yes", "no", "None"} {
if nv[1] == v {
valid = true
}
}
if !valid {
return errors.New("please povide rotateKeys=yes|no|None")
}
cr.Spec.RotateKeys = nv[1]
default:
return errors.New("Please enter one of: profile, storageClassName,rotateKeys, manifestRoot, git to configure the current context")
return errors.New("Please enter one of: profile, storageClassName, manifestRoot, git to configure the current context")
}
return nil
}

View File

@@ -96,7 +96,6 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
@@ -244,7 +243,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"profile=minikube", "rotateKeys=yes", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
args: []string{"profile=minikube", "storageClassName=efs", "opsRunner.enabled=yes", "opsRunner.schedule=30 * * * *", "git.repository=master", "git.userName=foo", "git.accessToken=1234"},
},
wantErr: false,
},
@@ -254,7 +253,7 @@ func TestSetOtherConfigs(t *testing.T) {
q: &Qliksense{
QliksenseHome: testDir,
},
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar", "rotateKeys=bar"},
args: []string{"someconfig=somevalue, opsRunner.schedule=bar", "opsRunner.enabled=bar", "git.foo=bar"},
},
wantErr: true,
},
@@ -744,7 +743,6 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik-default
`
qlikDefaultContext := "qlik-default"
@@ -763,7 +761,6 @@ metadata:
name: qlik1
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik1`
contextYaml2 :=
@@ -774,7 +771,6 @@ metadata:
name: qlik2
spec:
profile: docker-desktop
rotateKeys: "yes"
releaseName: qlik2`
contextsDir := filepath.Join(testDir, contexts, "qlik1")

View File

@@ -16,7 +16,7 @@ func TestUnsetAll(t *testing.T) {
testPepareDir(qHome)
defer os.RemoveAll(qHome)
//fmt.Print(qHome)
args := []string{"rotateKeys", "qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula", "opsRunner.watchBranch"}
args := []string{"qliksense", "qliksense2.acceptEula3", "serviceA.acceptEula", "opsRunner.watchBranch"}
//args := []string{"opsRunner"}
//args := []string{"opsRunner.watchBranch"}
if err := unsetAll(qHome, args); err != nil {
@@ -29,10 +29,6 @@ func TestUnsetAll(t *testing.T) {
t.Log("error while getting current cr", err)
t.FailNow()
}
if qcr.Spec.RotateKeys != "" {
t.Log("Expected empty rotateKeys but got: " + qcr.Spec.RotateKeys)
t.Fail()
}
if qcr.Spec.Configs["qliksense"] != nil {
t.Log("qliksense in configs not deleted")
@@ -82,7 +78,6 @@ metadata:
name: qlik-default
spec:
profile: docker-desktop
rotateKeys: "yes"
opsRunner:
enabled: "yes"
watchBranch: something

View File

@@ -93,11 +93,15 @@ func getQliksenseInitCrds(qcr *qapi.QliksenseCR) (string, error) {
}
}
qInitMsPath := filepath.Join(repoPath, Q_INIT_CRD_PATH)
qInitMsPath := filepath.Join(repoPath, "manifests", qcr.Spec.Profile, "crds")
if _, err := os.Lstat(qInitMsPath); err != nil {
// older version of qliksense-init used
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
qInitMsPath = filepath.Join(repoPath, Q_INIT_CRD_PATH)
if _, err := os.Lstat(qInitMsPath); err != nil {
// older version of qliksense-init used
qInitMsPath = filepath.Join(repoPath, "manifests/base/manifests/qliksense-init")
}
}
qInitByte, err := ExecuteKustomizeBuild(qInitMsPath)
if err != nil {
fmt.Println("cannot generate crds for qliksense-init", err)

View File

@@ -268,7 +268,6 @@ spec:
- name: imageRegistry
value: %s
manifestsRoot: %s
rotateKeys: "yes"
releaseName: qlik-default
`, version, registry.url, manifestsRootDir)
setupQliksenseTestDefaultContext(t, tmpQlikSenseHome, cr)

View File

@@ -7,7 +7,9 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mattn/go-tty"
@@ -22,12 +24,12 @@ import (
type InstallCommandOptions struct {
StorageClass string
MongodbUri string
RotateKeys string
AcceptEULA string
DryRun bool
Pull bool
Push bool
CleanPatchFiles bool
RotateKeys bool
}
const (
@@ -45,6 +47,14 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return err
}
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
} else if qcr, err = qConfig.GetCurrentCR(); err != nil {
return err
}
}
if opts.AcceptEULA != "" && opts.AcceptEULA != "yes" {
enforceEula()
} else if opts.AcceptEULA == "" && !qcr.IsEULA() {
@@ -58,8 +68,9 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
if opts.StorageClass != "" {
qcr.Spec.StorageClassName = opts.StorageClass
}
if opts.RotateKeys != "" {
qcr.Spec.RotateKeys = opts.RotateKeys
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
return err
}
if opts.CleanPatchFiles {
@@ -71,13 +82,11 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
// for debugging purpose
if opts.DryRun {
// generate patches
qcr.Spec.RotateKeys = "None"
userHomeDir, _ := homedir.Dir()
fmt.Println("Generating patches only")
cr.GeneratePatches(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config"))
cr.GeneratePatches(&qcr.KApiCr, config.KeysActionDoNothing, path.Join(userHomeDir, ".kube", "config"))
return nil
}
qConfig.WriteCurrentContextCR(qcr)
if installed, err := q.CheckAllCrdsInstalled(); err != nil {
fmt.Println("error verifying whether CRDs are installed", err)
@@ -123,6 +132,18 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return err
}
if opts.RotateKeys {
fmt.Println("Deleting stored application keys")
if err := q.DeleteKeysClusterBackup(); err != nil {
return err
} else {
qcr.AddLabelToCr("keys-rotated", strconv.FormatInt(time.Now().Unix(), 10))
if err := qConfig.WriteCurrentContextCR(qcr); err != nil {
return err
}
}
}
if qcr.Spec.OpsRunner != nil {
// fetching and applying manifest will be in the operator controller
// get decrypted cr
@@ -132,49 +153,19 @@ func (q *Qliksense) InstallQK8s(version string, opts *InstallCommandOptions) err
return q.applyCR(dcr)
}
}
if !qcr.IsRepoExist() {
if err := fetchAndUpdateCR(qConfig, version); err != nil {
return err
}
}
if qcr.Spec.GetManifestsRoot() == "" {
return errors.New("cannot get the manifest root. Use qliksense fetch <version> or qliksense set manifestsRoot")
}
// install generated manifests into cluster
fmt.Println("Installing generated manifests into the cluster")
if dcr, err := qConfig.GetDecryptedCr(qcr); err != nil {
return err
} else if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
if IsQliksenseInstalled(dcr.GetName()) {
return q.UpgradeQK8s(opts.CleanPatchFiles)
}
if err := q.applyConfigToK8s(dcr); err != nil {
fmt.Println("cannot do kubectl apply on manifests")
return err
} else {
return q.applyCR(dcr)
}
return q.applyCR(dcr)
}
}
func IsQliksenseInstalled(crName string) bool {
args := []string{
"get",
"qliksense",
crName,
"-ogo-template",
`--template='{{ .metadata.name}}'`,
}
_, err := qapi.KubectlDirectOps(args, "")
if err != nil {
return false
}
return true
}
func (q *Qliksense) getProcessedOperatorControllerString(qcr *qapi.QliksenseCR) (string, error) {
operatorControllerString := q.GetOperatorControllerString()
if imageRegistry := qcr.Spec.GetImageRegistry(); imageRegistry != "" {

View File

@@ -8,13 +8,12 @@ import (
"strings"
"testing"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"github.com/gobuffalo/packr/v2"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func TestCreateK8sResourceBeforePatch(t *testing.T) {
@@ -44,8 +43,7 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
profile: docker-desktop`
q := New(testDir)
if err := q.LoadCr([]byte(sampleCr), false); err != nil {

21
pkg/qliksense/keys.go Normal file
View File

@@ -0,0 +1,21 @@
package qliksense
import (
"path"
"github.com/mitchellh/go-homedir"
"github.com/qlik-oss/k-apis/pkg/cr"
qapi "github.com/qlik-oss/sense-installer/pkg/api"
)
func (q *Qliksense) DeleteKeysClusterBackup() error {
qConfig := qapi.NewQConfig(q.QliksenseHome)
if qcr, err := qConfig.GetCurrentCR(); err != nil {
return err
} else if userHomeDir, err := homedir.Dir(); err != nil {
return err
} else if err := cr.DeleteKeysClusterBackup(&qcr.KApiCr, path.Join(userHomeDir, ".kube", "config")); err != nil {
return err
}
return nil
}

View File

@@ -276,9 +276,11 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
configPath := filepath.Join(tmpDir, "config")
if repo, err := kapis_git.CloneRepository(configPath, defaultConfigRepoGitUrl, nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "", nil); err != nil {
} else if err := kapis_git.Checkout(repo, "e38df644e759abf0b5941c1511d1a2cd5e3c42fa", "commit-e38df644e759abf0b5941c1511d1a2cd5e3c42fa", nil); err != nil {
t.Fatalf("unexpected error: %v\n", err)
}
//tmpDir := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230"
//configPath := "/var/folders/mf/5hs1qkq508q_scjbhxhmf9qwjrp346/T/679268230/config"
cr := &config.CRSpec{
ManifestsRoot: configPath,
@@ -310,8 +312,8 @@ func Test_executeKustomizeBuild_onQlikConfig_regenerateKeys(t *testing.T) {
}
break
}
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface {}]interface {})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface {}]interface {})["tokenAuthPrivateKeyId"].(string)
if resource["kind"].(string) == "Secret" && strings.Contains(resource["metadata"].(map[interface{}]interface{})["name"].(string), "users-secrets-") {
keyIdBase64 = resource["data"].(map[interface{}]interface{})["tokenAuthPrivateKeyId"].(string)
break
}
}

View File

@@ -34,8 +34,7 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
profile: docker-desktop`
sampleCr2 := `
apiVersion: qlik.com/v1
kind: Qliksense
@@ -61,8 +60,7 @@ spec:
qliksense:
- name: mongodbUri
value: mongodb://qlik-default-mongodb:27017/qliksense?ssl=false
profile: docker-desktop
rotateKeys: "yes"`
profile: docker-desktop`
duplicateCr := `
apiVersion: qlik.com/v1

View File

@@ -25,7 +25,6 @@ func (q *Qliksense) UpgradeQK8s(cleanPatchFiles bool) error {
fmt.Println("cannot get the current-context cr", err)
return err
}
qcr.Spec.RotateKeys = "no"
dcr, err := qConfig.GetDecryptedCr(qcr)
if err != nil {