Skip to content

Commit

Permalink
fix: Add support to manage token based auth (#527)
Browse files Browse the repository at this point in the history
## What type of PR is this?

/kind refactor

## What this PR does / why we need it:

If uploading a token based kubeconfig, error reported as invalid
kubeconfig.

In fact, token based kubeconfig is general usage in many cases.

ref:
https://kb.leaseweb.com/products/kubernetes/users-roles-and-permissions-on-kubernetes-rbac/create-a-token-based-kubeconfig
  • Loading branch information
peter-wangxu authored Jul 8, 2024
1 parent 916cd76 commit 8dabf6a
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ endif
.PHONY: test
test: ## Run the tests
@PKG_LIST=$${TARGET_PKG:-$(GOSOURCE_PATHS)}; \
go test -gcflags=all=-l -timeout=10m `go list $${PKG_LIST} | grep -vE "internalimport|generated|handler"` ${TEST_FLAGS}
go test -gcflags=all=-l -timeout=10m `go list -e $${PKG_LIST} | grep -vE "internalimport|generated|handler"` ${TEST_FLAGS}


# cover: Generates a coverage report for the specified TARGET_PKG or default GOSOURCE_PATHS.
Expand Down
3 changes: 3 additions & 0 deletions pkg/core/handler/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ func ValidateKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc {
// Decode the request body into the payload.
payload := &ValidatePayload{}
if err := payload.Decode(r); err != nil {
log.Error(err, "failed to decode kubeconfig")
render.Render(w, r, handler.FailureResponse(ctx, err))
return
}
Expand All @@ -378,6 +379,8 @@ func ValidateKubeConfig(clusterMgr *cluster.ClusterManager) http.HandlerFunc {
if info, err := clusterMgr.ValidateKubeConfigWithYAML(ctx, payload.KubeConfig); err == nil {
render.JSON(w, r, handler.SuccessResponse(ctx, info))
} else {
log.Error(err, "failed to validate kubeconfig")
w.WriteHeader(http.StatusBadRequest)
render.Render(w, r, handler.FailureResponse(ctx, err))
}
}
Expand Down
15 changes: 11 additions & 4 deletions pkg/core/manager/cluster/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,13 +418,20 @@ func (c *ClusterManager) ValidateKubeConfigFor(
if cluster.Server == "" {
return "", ErrMissingClusterServer
}
if cluster.CertificateAuthorityData == "" {

if cluster.Insecure && len(cluster.CertificateAuthorityData) > 0 {
return "", ErrBothInsecureAndCertificateAuthority
}

if !cluster.Insecure && len(cluster.CertificateAuthorityData) <= 0 {
// when insecure is false, CA is required.
return "", ErrMissingCertificateAuthority
}
if _, err := base64.StdEncoding.DecodeString(cluster.CertificateAuthorityData); err != nil {
return "", ErrInvalidCertificateAuthority
if len(cluster.CertificateAuthorityData) > 0 {
if _, err := base64.StdEncoding.DecodeString(cluster.CertificateAuthorityData); err != nil {
return "", ErrInvalidCertificateAuthority
}
}

// Check cluster server address connectivity
if err := checkEndpointConnectivity(cluster.Server); err != nil {
return "", ErrClusterServerConnectivity
Expand Down
46 changes: 46 additions & 0 deletions pkg/core/manager/cluster/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,29 @@ users:
- name: test-user
user:
token: 6b26************************99af
`,
expectedErr: false,
},
{
name: "Sanitize token based kubeconfig",
plainKubeConfig: newMockTokenAndInsecureKubeConfig(),
expectedSanitizedKubeConfig: `apiVersion: v1
kind: Config
clusters:
- name: cluster-local
cluster:
insecure-skip-tls-verify: true
server: https://127.0.0.1:30383/k8s/cluster/cluster-local
contexts:
- name: cluster-local-admin@cluster-local
context:
cluster: cluster-local
user: cluster-local-admin
current-context: cluster-local-admin@cluster-local
users:
- name: cluster-local-admin
user:
token: 3ac4************************ddd5
`,
expectedErr: false,
},
Expand Down Expand Up @@ -660,6 +683,29 @@ current-context: test-context
`, base64.StdEncoding.EncodeToString([]byte("sensitive-certificate-data")))
}

// newMockTokenAndInsecureKubeConfig generates a mock token-based kubeconfig with sensitive information
// included.
func newMockTokenAndInsecureKubeConfig() string {
return fmt.Sprintf(`apiVersion: v1
kind: Config
clusters:
- name: cluster-local
cluster:
insecure-skip-tls-verify: true
server: https://127.0.0.1:30383/k8s/cluster/cluster-local
contexts:
- context:
cluster: cluster-local
user: cluster-local-admin
name: cluster-local-admin@cluster-local
current-context: cluster-local-admin@cluster-local
users:
- name: cluster-local-admin
user:
token: %s
`, base64.StdEncoding.EncodeToString([]byte("sensitive-token-data")))
}

// newMockCluster is a helper function that creates a mock cluster unstructured
// object.
func newMockCluster(name string) *unstructured.Unstructured {
Expand Down
2 changes: 2 additions & 0 deletions pkg/core/manager/cluster/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
ErrMissingUserEntry = errors.New("at least one user entry is required")
ErrMissingClusterName = errors.New("cluster name is required")
ErrMissingClusterServer = errors.New("cluster server is required")
ErrBothInsecureAndCertificateAuthority = errors.New("certificate-authority-data and insecure-skip-tls-verify couldn't both be set")
ErrMissingCertificateAuthority = errors.New("certificate-authority-data is required")
ErrInvalidCertificateAuthority = errors.New("certificate-authority-data is invalid")
ErrClusterServerConnectivity = errors.New("cannot connect to the cluster server")
Expand Down Expand Up @@ -70,6 +71,7 @@ type ClusterEntry struct {
//
//nolint:tagliatelle
type Cluster struct {
Insecure bool `yaml:"insecure-skip-tls-verify,omitempty"`
Server string `yaml:"server"`
CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"`
}
Expand Down
25 changes: 16 additions & 9 deletions pkg/core/manager/cluster/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,33 @@ func checkEndpointConnectivity(endpoint string) error {
func buildClientConfigFromKubeConfig(config *KubeConfig) (*rest.Config, error) {
// Create an initial rest.Config object.
clientConfig := &rest.Config{}

// Set the API server and authentication details.
if len(config.Clusters) > 0 {
cluster := config.Clusters[0].Cluster
clientConfig.Host = cluster.Server
if plain, err := base64.StdEncoding.DecodeString(cluster.CertificateAuthorityData); err != nil {
return nil, errors.Wrapf(
err,
"invalid certificate-authority-data for cluster %s",
config.Clusters[0].Name,
)
} else {
clientConfig.CAData = plain
clientConfig.Insecure = cluster.Insecure // allow insecure connection
if len(cluster.CertificateAuthorityData) > 0 {
if plain, err := base64.StdEncoding.DecodeString(cluster.CertificateAuthorityData); err != nil {
return nil, errors.Wrapf(
err,
"invalid certificate-authority-data for cluster %s",
config.Clusters[0].Name,
)
} else {
clientConfig.CAData = plain
}
}
}

if len(config.Users) > 0 {
user := config.Users[0].User
clientConfig.Username = user.Username
clientConfig.Password = user.Password
clientConfig.BearerToken = user.Token
if len(user.Token) > 0 || (len(user.Password) > 0 && len(user.Username) > 0) {
// prefer token or username/password
return clientConfig, nil
}
if plain, err := base64.StdEncoding.DecodeString(user.ClientCertificateData); err != nil {
return nil, fmt.Errorf(
"invalid client-certificate-data for user %s: %v",
Expand Down

0 comments on commit 8dabf6a

Please sign in to comment.