diff --git a/pkg/k8s/kubeconfig/errors.go b/pkg/k8s/kubeconfig/errors.go
index 8f65b9576..8e9a1aa37 100644
--- a/pkg/k8s/kubeconfig/errors.go
+++ b/pkg/k8s/kubeconfig/errors.go
@@ -14,6 +14,10 @@
 
 package kubeconfig
 
+import (
+	"fmt"
+)
+
 // ErrKubeConfigPathEmpty returned when kubeconfig path is not specified
 type ErrKubeConfigPathEmpty struct {
 }
@@ -21,3 +25,27 @@ type ErrKubeConfigPathEmpty struct {
 func (e *ErrKubeConfigPathEmpty) Error() string {
 	return "kubeconfig path is not defined"
 }
+
+// ErrClusterNameEmpty returned when cluster name is not provided
+type ErrClusterNameEmpty struct {
+}
+
+func (e ErrClusterNameEmpty) Error() string {
+	return "cluster name is not defined"
+}
+
+// ErrMalformedSecret error returned if secret data value is lost or empty
+type ErrMalformedSecret struct {
+	ClusterName string
+	Namespace   string
+	SecretName  string
+}
+
+func (e ErrMalformedSecret) Error() string {
+	return fmt.Sprintf(
+		"can't retrieve data from secret %s in cluster %s(namespace: %s)",
+		e.SecretName,
+		e.ClusterName,
+		e.Namespace,
+	)
+}
diff --git a/pkg/k8s/kubeconfig/kubeconfig.go b/pkg/k8s/kubeconfig/kubeconfig.go
index 505014a0a..0caa04fba 100644
--- a/pkg/k8s/kubeconfig/kubeconfig.go
+++ b/pkg/k8s/kubeconfig/kubeconfig.go
@@ -88,6 +88,13 @@ func FromAPIalphaV1(apiObj *v1alpha1.KubeConfig) KubeSourceFunc {
 	}
 }
 
+// FromSecret returns KubeSource type, uses client interface to kubernetes cluster
+func FromSecret(kubeOpts *FromClusterOptions) KubeSourceFunc {
+	return func() ([]byte, error) {
+		return GetKubeconfigFromSecret(kubeOpts)
+	}
+}
+
 // FromFile returns KubeSource type, uses path to kubeconfig on FS as source to construct kubeconfig object
 func FromFile(path string, fs document.FileSystem) KubeSourceFunc {
 	return func() ([]byte, error) {
diff --git a/pkg/k8s/kubeconfig/kubeconfig_test.go b/pkg/k8s/kubeconfig/kubeconfig_test.go
index 170558cce..28cab6071 100644
--- a/pkg/k8s/kubeconfig/kubeconfig_test.go
+++ b/pkg/k8s/kubeconfig/kubeconfig_test.go
@@ -23,16 +23,22 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	coreV1 "k8s.io/api/core/v1"
+	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1 "k8s.io/client-go/tools/clientcmd/api/v1"
 	kustfs "sigs.k8s.io/kustomize/api/filesys"
 
 	"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
 	"opendev.org/airship/airshipctl/pkg/document"
+	"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
 	"opendev.org/airship/airshipctl/pkg/k8s/kubeconfig"
 	"opendev.org/airship/airshipctl/testutil/fs"
 )
 
 const (
+	testClusterName     = "dummy_target_cluster"
+	testSecretName      = testClusterName + "-kubeconfig"
+	testNamespace       = "default"
 	testValidKubeconfig = `apiVersion: v1
 clusters:
 - cluster:
@@ -130,6 +136,171 @@ func TestKubeconfigContent(t *testing.T) {
 	assert.Equal(t, expectedData, actualData)
 }
 
+func TestFromSecret(t *testing.T) {
+	tests := []struct {
+		name         string
+		opts         *kubeconfig.FromClusterOptions
+		acc          fake.ResourceAccumulator
+		expectedData []byte
+		err          error
+	}{
+		{
+			name: "valid kubeconfig",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+				Data: map[string][]byte{
+					"value": []byte(testValidKubeconfig),
+				},
+			}),
+			expectedData: []byte(testValidKubeconfig),
+			err:          nil,
+		},
+		{
+			name: "no cluster name",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: "",
+				Namespace:   testNamespace,
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+				Data: map[string][]byte{
+					"value": []byte(testValidKubeconfig),
+				},
+			}),
+			expectedData: nil,
+			err:          kubeconfig.ErrClusterNameEmpty{},
+		},
+		{
+			name: "default namespace",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: testClusterName,
+				Namespace:   "",
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+				Data: map[string][]byte{
+					"value": []byte(testValidKubeconfig),
+				},
+			}),
+			expectedData: []byte(testValidKubeconfig),
+			err:          nil,
+		},
+		{
+			name: "no data in secret",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+			}),
+			expectedData: nil,
+			err: kubeconfig.ErrMalformedSecret{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+				SecretName:  testSecretName,
+			},
+		},
+		{
+			name: "empty data in secret",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+				Data: map[string][]byte{},
+			}),
+			expectedData: nil,
+			err: kubeconfig.ErrMalformedSecret{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+				SecretName:  testSecretName,
+			},
+		},
+		{
+			name: "empty value in data in secret",
+			opts: &kubeconfig.FromClusterOptions{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+			},
+			acc: fake.WithTypedObjects(&coreV1.Secret{
+				TypeMeta: metaV1.TypeMeta{
+					Kind:       "Secret",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metaV1.ObjectMeta{
+					Name:      testSecretName,
+					Namespace: testNamespace,
+				},
+				Data: map[string][]byte{
+					"value": []byte(""),
+				},
+			}),
+			expectedData: nil,
+			err: kubeconfig.ErrMalformedSecret{
+				ClusterName: testClusterName,
+				Namespace:   testNamespace,
+				SecretName:  testSecretName,
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			tt.opts.Client = fake.NewClient(tt.acc)
+			kubeconf, err := kubeconfig.FromSecret(tt.opts)()
+			if tt.err != nil {
+				assert.Equal(t, tt.err, err)
+				assert.Nil(t, kubeconf)
+			} else {
+				require.NoError(t, err)
+				assert.Equal(t, tt.expectedData, kubeconf)
+			}
+		})
+	}
+}
+
 func TestFromBundle(t *testing.T) {
 	tests := []struct {
 		name         string
@@ -171,6 +342,7 @@ func TestFromBundle(t *testing.T) {
 		})
 	}
 }
+
 func TestNewKubeConfig(t *testing.T) {
 	tests := []struct {
 		shouldPanic           bool
diff --git a/pkg/k8s/kubeconfig/secret.go b/pkg/k8s/kubeconfig/secret.go
new file mode 100644
index 000000000..4c28bfa2e
--- /dev/null
+++ b/pkg/k8s/kubeconfig/secret.go
@@ -0,0 +1,72 @@
+/*
+ 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
+
+     https://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 kubeconfig
+
+import (
+	"fmt"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"opendev.org/airship/airshipctl/pkg/k8s/client"
+	"opendev.org/airship/airshipctl/pkg/log"
+)
+
+// FromClusterOptions holds all configurable options for kubeconfig extraction
+type FromClusterOptions struct {
+	ClusterName string
+	Namespace   string
+	Client      client.Interface
+}
+
+// GetKubeconfigFromSecret extracts kubeconfig from secret data structure
+func GetKubeconfigFromSecret(o *FromClusterOptions) ([]byte, error) {
+	const defaultNamespace = "default"
+
+	if o.ClusterName == "" {
+		return nil, ErrClusterNameEmpty{}
+	}
+	if o.Namespace == "" {
+		log.Printf("Namespace is not provided, using default one")
+		o.Namespace = defaultNamespace
+	}
+
+	log.Debugf("Extracting kubeconfig from secret in cluster %s(namespace: %s)", o.ClusterName, o.Namespace)
+	secretName := fmt.Sprintf("%s-kubeconfig", o.ClusterName)
+	kubeCore := o.Client.ClientSet().CoreV1()
+
+	secret, err := kubeCore.Secrets(o.Namespace).Get(secretName, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	if secret.Data == nil {
+		return nil, ErrMalformedSecret{
+			ClusterName: o.ClusterName,
+			Namespace:   o.Namespace,
+			SecretName:  secretName,
+		}
+	}
+
+	val, exist := secret.Data["value"]
+	if !exist || len(val) == 0 {
+		return nil, ErrMalformedSecret{
+			ClusterName: o.ClusterName,
+			Namespace:   o.Namespace,
+			SecretName:  secretName,
+		}
+	}
+
+	return val, nil
+}