diff options
Diffstat (limited to 'src/k8splugin/internal')
-rw-r--r-- | src/k8splugin/internal/app/client.go | 146 | ||||
-rw-r--r-- | src/k8splugin/internal/app/client_test.go | 6 | ||||
-rw-r--r-- | src/k8splugin/internal/app/instance.go | 4 | ||||
-rw-r--r-- | src/k8splugin/internal/auth/auth.go | 107 | ||||
-rw-r--r-- | src/k8splugin/internal/auth/auth_test.go | 47 |
5 files changed, 294 insertions, 16 deletions
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index fa5fdfd5..cd1ec8a2 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -21,17 +21,30 @@ import ( utils "k8splugin/internal" pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" "k8s.io/helm/pkg/tiller" ) -type kubernetesClient struct { - clientSet *kubernetes.Clientset +// KubernetesResource is the interface that is implemented +type KubernetesResource interface { + Create(yamlFilePath string, namespace string, client *KubernetesClient) (string, error) + Delete(kind string, name string, namespace string, client *KubernetesClient) error +} + +type KubernetesClient struct { + clientSet *kubernetes.Clientset + dynamicClient dynamic.Interface + discoverClient *discovery.DiscoveryClient + restMapper meta.RESTMapper } // GetKubeClient loads the Kubernetes configuation values stored into the local configuration file -func (k *kubernetesClient) init(configPath string) error { +func (k *KubernetesClient) init(configPath string) error { if configPath == "" { return pkgerrors.New("config not passed and is not found in ~/.kube. ") } @@ -46,10 +59,20 @@ func (k *kubernetesClient) init(configPath string) error { return err } + k.dynamicClient, err = dynamic.NewForConfig(config) + if err != nil { + return pkgerrors.Wrap(err, "Creating dynamic client") + } + + k.discoverClient, err = discovery.NewDiscoveryClientForConfig(config) + if err != nil { + return pkgerrors.Wrap(err, "Creating discovery client") + } + return nil } -func (k *kubernetesClient) ensureNamespace(namespace string) error { +func (k *KubernetesClient) ensureNamespace(namespace string) error { namespacePlugin, ok := utils.LoadedPlugins["namespace"] if !ok { return pkgerrors.New("No plugin for namespace resource found") @@ -82,7 +105,51 @@ func (k *kubernetesClient) ensureNamespace(namespace string) error { return nil } -func (k *kubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) { +func (k *KubernetesClient) createGeneric(kind string, files []string, namespace string) ([]string, error) { + + log.Println("Processing items of Kind: " + kind) + + //Check if have the mapper before loading the plugin + err := k.updateMapper() + if err != nil { + return nil, pkgerrors.Wrap(err, "Unable to create RESTMapper") + } + + pluginObject, ok := utils.LoadedPlugins["generic"] + if !ok { + return nil, pkgerrors.New("No generic plugin found") + } + + symbol, err := pluginObject.Lookup("ExportedVariable") + if err != nil { + return nil, pkgerrors.Wrap(err, "No ExportedVariable symbol found") + } + + genericPlugin, ok := symbol.(KubernetesResource) + if !ok { + return nil, pkgerrors.New("ExportedVariable is not KubernetesResource type") + } + + //Iterate over each file of a particular kind here + var resourcesCreated []string + for _, f := range files { + if _, err := os.Stat(f); os.IsNotExist(err) { + return nil, pkgerrors.New("File " + f + "does not exists") + } + + log.Println("Processing file: " + f) + + name, err := genericPlugin.Create(f, namespace, k) + if err != nil { + return nil, pkgerrors.Wrap(err, "Error in generic plugin") + } + + resourcesCreated = append(resourcesCreated, name) + } + return resourcesCreated, nil +} + +func (k *KubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) { log.Println("Processing items of Kind: " + kind) @@ -103,7 +170,8 @@ func (k *kubernetesClient) createKind(kind string, files []string, namespace str typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)] if !ok { - return nil, pkgerrors.New("No plugin for kind " + kind + " found") + log.Println("No plugin for kind " + kind + " found. Using generic Plugin") + return k.createGeneric(kind, files, namespace) } symCreateResourceFunc, err := typePlugin.Lookup("Create") @@ -123,7 +191,7 @@ func (k *kubernetesClient) createKind(kind string, files []string, namespace str return resourcesCreated, nil } -func (k *kubernetesClient) createResources(resMap map[string][]string, +func (k *KubernetesClient) createResources(resMap map[string][]string, namespace string) (map[string][]string, error) { err := k.ensureNamespace(namespace) @@ -163,17 +231,47 @@ func (k *kubernetesClient) createResources(resMap map[string][]string, return createdResourceMap, nil } -func (k *kubernetesClient) deleteKind(kind string, resources []string, namespace string) error { +func (k *KubernetesClient) deleteGeneric(kind string, resources []string, namespace string) error { + log.Println("Deleting items of Kind: " + kind) + + pluginObject, ok := utils.LoadedPlugins["generic"] + if !ok { + return pkgerrors.New("No generic plugin found") + } + + symbol, err := pluginObject.Lookup("ExportedVariable") + if err != nil { + return pkgerrors.Wrap(err, "No ExportedVariable symbol found") + } + + //Assert that it implements the KubernetesResource + genericPlugin, ok := symbol.(KubernetesResource) + if !ok { + return pkgerrors.New("ExportedVariable is not KubernetesResource type") + } + + for _, res := range resources { + err = genericPlugin.Delete(kind, res, namespace, k) + if err != nil { + return pkgerrors.Wrap(err, "Error in generic plugin") + } + } + + return nil +} + +func (k *KubernetesClient) deleteKind(kind string, resources []string, namespace string) error { log.Println("Deleting items of Kind: " + kind) typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)] if !ok { - return pkgerrors.New("No plugin for resource " + kind + " found") + log.Println("No plugin for kind " + kind + " found. Using generic Plugin") + return k.deleteGeneric(kind, resources, namespace) } symDeleteResourceFunc, err := typePlugin.Lookup("Delete") if err != nil { - return pkgerrors.Wrap(err, "Error fetching "+kind+" plugin") + return pkgerrors.Wrap(err, "Error findinf Delete symbol in plugin") } for _, res := range resources { @@ -187,7 +285,7 @@ func (k *kubernetesClient) deleteKind(kind string, resources []string, namespace return nil } -func (k *kubernetesClient) deleteResources(resMap map[string][]string, namespace string) error { +func (k *KubernetesClient) deleteResources(resMap map[string][]string, namespace string) error { //TODO: Investigate if deletion should be in a particular order for kind, resourceNames := range resMap { err := k.deleteKind(kind, resourceNames, namespace) @@ -198,3 +296,29 @@ func (k *kubernetesClient) deleteResources(resMap map[string][]string, namespace return nil } + +func (k *KubernetesClient) updateMapper() error { + //Create restMapper if not already done + if k.restMapper != nil { + return nil + } + + groupResources, err := restmapper.GetAPIGroupResources(k.discoverClient) + if err != nil { + return pkgerrors.Wrap(err, "Get GroupResources") + } + + k.restMapper = restmapper.NewDiscoveryRESTMapper(groupResources) + return nil +} + +//GetMapper returns the RESTMapper that was created for this client +func (k *KubernetesClient) GetMapper() meta.RESTMapper { + return k.restMapper +} + +//GetDynamicClient returns the dynamic client that is needed for +//unstructured REST calls to the apiserver +func (k *KubernetesClient) GetDynamicClient() dynamic.Interface { + return k.dynamicClient +} diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go index 5999cfa0..b3436431 100644 --- a/src/k8splugin/internal/app/client_test.go +++ b/src/k8splugin/internal/app/client_test.go @@ -45,7 +45,7 @@ func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error { func TestInit(t *testing.T) { t.Run("Successfully create Kube Client", func(t *testing.T) { - kubeClient := kubernetesClient{} + kubeClient := KubernetesClient{} err := kubeClient.init("../../mock_files/mock_configs/mock_config") if err != nil { t.Fatalf("TestGetKubeClient returned an error (%s)", err) @@ -71,7 +71,7 @@ func TestCreateResources(t *testing.T) { t.Fatalf("LoadMockPlugins returned an error (%s)", err) } - k8 := kubernetesClient{ + k8 := KubernetesClient{ clientSet: &kubernetes.Clientset{}, } @@ -100,7 +100,7 @@ func TestDeleteResources(t *testing.T) { t.Fatalf("LoadMockPlugins returned an error (%s)", err) } - k8 := kubernetesClient{ + k8 := KubernetesClient{ clientSet: &kubernetes.Clientset{}, } diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go index a5b35fef..93305c30 100644 --- a/src/k8splugin/internal/app/instance.go +++ b/src/k8splugin/internal/app/instance.go @@ -118,7 +118,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts") } - k8sClient := kubernetesClient{} + k8sClient := KubernetesClient{} err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + i.CloudRegion) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") @@ -183,7 +183,7 @@ func (v *InstanceClient) Delete(id string) error { return pkgerrors.Wrap(err, "Error getting Instance") } - k8sClient := kubernetesClient{} + k8sClient := KubernetesClient{} err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + inst.CloudRegion) if err != nil { return pkgerrors.Wrap(err, "Getting CloudRegion Information") diff --git a/src/k8splugin/internal/auth/auth.go b/src/k8splugin/internal/auth/auth.go new file mode 100644 index 00000000..3da8f2af --- /dev/null +++ b/src/k8splugin/internal/auth/auth.go @@ -0,0 +1,107 @@ +/* + * 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 ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "io/ioutil" + "log" + + pkgerrors "github.com/pkg/errors" +) + +// 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 err != nil { + return nil, pkgerrors.Wrap(err, "Read CA Cert file") + } + + 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 err != nil { + return nil, pkgerrors.Wrap(err, "Read Cert File") + } + + keyPEMBlk, err := readPEMBlock(keyFile) + if err != nil { + return nil, pkgerrors.Wrap(err, "Read Key File") + } + + tlsConfig.Certificates = make([]tls.Certificate, 1) + tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlk, keyPEMBlk) + if err != nil { + return nil, pkgerrors.Wrap(err, "Load x509 cert and key") + } + + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil +} + +func readPEMBlock(filename string) ([]byte, error) { + + pemData, err := ioutil.ReadFile(filename) + if err != nil { + return nil, pkgerrors.Wrap(err, "Read PEM File") + } + + pemBlock, rest := pem.Decode(pemData) + if len(rest) > 0 { + log.Println("Pemfile has extra data") + } + + if x509.IsEncryptedPEMBlock(pemBlock) { + password, err := ioutil.ReadFile(filename + ".pass") + if err != nil { + return nil, pkgerrors.Wrap(err, "Read Password File") + } + + pByte, err := base64.StdEncoding.DecodeString(string(password)) + if err != nil { + return nil, pkgerrors.Wrap(err, "Decode PEM Password") + } + + pemData, err = x509.DecryptPEMBlock(pemBlock, pByte) + if err != nil { + return nil, pkgerrors.Wrap(err, "Decrypt PEM Data") + } + 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 +} diff --git a/src/k8splugin/internal/auth/auth_test.go b/src/k8splugin/internal/auth/auth_test.go new file mode 100644 index 00000000..49494eee --- /dev/null +++ b/src/k8splugin/internal/auth/auth_test.go @@ -0,0 +1,47 @@ +/* +* Copyright 2018 TechMahindra +* +* 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 ( + "crypto/tls" + "testing" +) + +//Unit test to varify GetTLSconfig func and varify the tls config min version to be 771 +//Assuming cert file name as auth_test.cert +func TestGetTLSConfig(t *testing.T) { + _, err := GetTLSConfig("filedoesnotexist.cert", "filedoesnotexist.cert", "filedoesnotexist.cert") + if err == nil { + t.Errorf("Test failed, expected error but got none") + } + tlsConfig, err := GetTLSConfig("../../mock_files/mock_certs/auth_test_certificate.pem", + "../../mock_files/mock_certs/auth_test_certificate.pem", + "../../mock_files/mock_certs/auth_test_key.pem") + if err != nil { + t.Fatal("Test Failed as GetTLSConfig returned error: " + err.Error()) + } + expected := tls.VersionTLS12 + actual := tlsConfig.MinVersion + if tlsConfig != nil { + if int(actual) != expected { + t.Errorf("Test Failed due to version mismatch") + } + if tlsConfig == nil { + t.Errorf("Test Failed due to GetTLSConfig returned nil") + } + } +} |