Skip to content

Commit

Permalink
Bootstrap kubeconfig controller (#586)
Browse files Browse the repository at this point in the history
* BootstrapKubeconfig CRD and Controller
  • Loading branch information
anusha94 authored Jun 23, 2022
1 parent e2ca564 commit 9e7924e
Show file tree
Hide file tree
Showing 30 changed files with 798 additions and 133 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN go mod download
COPY main.go main.go
COPY apis/ apis/
COPY controllers/ controllers/
COPY common/installer common/installer
COPY common/ common/
COPY agent/installer agent/installer

# Build
Expand Down
9 changes: 9 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ resources:
kind: K8sInstallerConfigTemplate
path: github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1
version: v1beta1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: cluster.x-k8s.io
group: infrastructure
kind: BootstrapKubeconfig
path: github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1
version: v1beta1
version: "3"
75 changes: 75 additions & 0 deletions apis/infrastructure/v1beta1/bootstrapkubeconfig_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2022 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package v1beta1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// DefaultClusterName is the cluster name used in the generated bootstrap-kubeconfig
DefaultClusterName = "default-cluster"

// DefaultContext is the context name used in the generated bootstrap-kubeconfig
DefaultContext = "default-context"

// DefaultNamespace is the namespace used in the generated bootstrap-kubeconfig
DefaultNamespace = "default"

// DefaultAuth is the auth in the generated bootstrap-kubeconfig
DefaultAuth = "default-auth"

// BootstrapTokenDescription is the bootstrap token description for the secret that is generated
BootstrapTokenDescription = "Token generated by cluster-api-provider-bringyourownhost"

// BootstrapTokenExtraGroups is the byoh group that has access to create CertificateSigningRequest
BootstrapTokenExtraGroups = "system:bootstrappers:byoh"
)

// BootstrapKubeconfigSpec defines the desired state of BootstrapKubeconfig
type BootstrapKubeconfigSpec struct {
// APIServer is the address of the kubernetes cluster (https://hostname:port).
APIServer string `json:"apiserver"`

// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
// +optional
// +kubebuilder:default=false
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`

// CertificateAuthorityData contains PEM-encoded certificate authority certificates.
CertificateAuthorityData string `json:"certificate-authority-data"`
}

// BootstrapKubeconfigStatus defines the observed state of BootstrapKubeconfig
type BootstrapKubeconfigStatus struct {
// BootstrapKubeconfigData is an optional reference to a bootstrap kubeconfig info
// for starting the host registration process
// +optional
BootstrapKubeconfigData *string `json:"bootstrapKubeconfigData,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// BootstrapKubeconfig is the Schema for the bootstrapkubeconfigs API
type BootstrapKubeconfig struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec BootstrapKubeconfigSpec `json:"spec,omitempty"`
Status BootstrapKubeconfigStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// BootstrapKubeconfigList contains a list of BootstrapKubeconfig
type BootstrapKubeconfigList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BootstrapKubeconfig `json:"items"`
}

func init() {
SchemeBuilder.Register(&BootstrapKubeconfig{}, &BootstrapKubeconfigList{})
}
94 changes: 94 additions & 0 deletions apis/infrastructure/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions common/bootstraptoken/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2022 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package bootstraptoken

import (
"fmt"
"time"

infrastructurev1beta1 "github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/apis/infrastructure/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
bootstraputil "k8s.io/cluster-bootstrap/token/util"
)

// GetTokenIDSecretFromBootstrapToken splits the token string and returns the tokenID and tokenSecret parts
func GetTokenIDSecretFromBootstrapToken(tokenStr string) (tokenID, tokenSecret string, err error) {
substrs := bootstraputil.BootstrapTokenRegexp.FindStringSubmatch(tokenStr)
if len(substrs) != 3 { // nolint: gomnd
return "", "", fmt.Errorf("the bootstrap token %q was not of the form %q", tokenStr, bootstrapapi.BootstrapTokenPattern)
}

return substrs[1], substrs[2], nil
}

// GenerateSecretFromBootstrapTokenStr builds the secret object from the token string
// It also adds default description and auth groups that can be used by the bootstrap-kubeconfig
func GenerateSecretFromBootstrapToken(tokenStr string, ttl time.Duration) (*v1.Secret, error) {
tokenID, tokenSecret, err := GetTokenIDSecretFromBootstrapToken(tokenStr)
if err != nil {
return nil, err
}
secretData := map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
bootstrapapi.BootstrapTokenExpirationKey: []byte(time.Now().UTC().Add(ttl).Format(time.RFC3339)),
bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"),
bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"),
bootstrapapi.BootstrapTokenDescriptionKey: []byte(infrastructurev1beta1.BootstrapTokenDescription),
bootstrapapi.BootstrapTokenExtraGroupsKey: []byte(infrastructurev1beta1.BootstrapTokenExtraGroups),
}

bootstrapSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: bootstraputil.BootstrapTokenSecretName(tokenID),
Namespace: metav1.NamespaceSystem,
},
Type: bootstrapapi.SecretTypeBootstrapToken,
Data: secretData,
}
return bootstrapSecret, nil
}

// GenerateBootstrapKubeconfigFromBootstrapToken creates a bootstrap kubeconfig object from the bootstrap token generated
// It also adds default cluster, context and auth info
func GenerateBootstrapKubeconfigFromBootstrapToken(tokenStr string, bootstrapKubeconfig *infrastructurev1beta1.BootstrapKubeconfig) (*clientcmdapi.Config, error) {
tokenID, tokenSecret, err := GetTokenIDSecretFromBootstrapToken(tokenStr)
if err != nil {
return nil, err
}

// Build resulting kubeconfig.
kubeconfigData := clientcmdapi.Config{
// Define a cluster stanza based on the bootstrap kubeconfig.
Clusters: map[string]*clientcmdapi.Cluster{infrastructurev1beta1.DefaultClusterName: {
Server: bootstrapKubeconfig.Spec.APIServer,
InsecureSkipTLSVerify: bootstrapKubeconfig.Spec.InsecureSkipTLSVerify,
CertificateAuthorityData: []byte(bootstrapKubeconfig.Spec.CertificateAuthorityData),
}},
// Define auth based on the obtained client cert.
AuthInfos: map[string]*clientcmdapi.AuthInfo{infrastructurev1beta1.DefaultAuth: {
Token: fmt.Sprintf(tokenID + "." + tokenSecret),
}},
// Define a context that connects the auth info and cluster, and set it as the default
Contexts: map[string]*clientcmdapi.Context{infrastructurev1beta1.DefaultContext: {
Cluster: infrastructurev1beta1.DefaultClusterName,
AuthInfo: infrastructurev1beta1.DefaultAuth,
Namespace: infrastructurev1beta1.DefaultNamespace,
}},
CurrentContext: infrastructurev1beta1.DefaultContext,
}

return &kubeconfigData, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.1
creationTimestamp: null
name: bootstrapkubeconfigs.infrastructure.cluster.x-k8s.io
spec:
group: infrastructure.cluster.x-k8s.io
names:
kind: BootstrapKubeconfig
listKind: BootstrapKubeconfigList
plural: bootstrapkubeconfigs
singular: bootstrapkubeconfig
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: BootstrapKubeconfig is the Schema for the bootstrapkubeconfigs
API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: BootstrapKubeconfigSpec defines the desired state of BootstrapKubeconfig
properties:
apiserver:
description: APIServer is the address of the kubernetes cluster (https://hostname:port).
type: string
certificate-authority-data:
description: CertificateAuthorityData contains PEM-encoded certificate
authority certificates.
type: string
insecure-skip-tls-verify:
default: false
description: InsecureSkipTLSVerify skips the validity check for the
server's certificate. This will make your HTTPS connections insecure.
type: boolean
required:
- apiserver
- certificate-authority-data
type: object
status:
description: BootstrapKubeconfigStatus defines the observed state of BootstrapKubeconfig
properties:
bootstrapKubeconfigData:
description: BootstrapKubeconfigData is an optional reference to a
bootstrap kubeconfig info for starting the host registration process
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
3 changes: 3 additions & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ resources:
- bases/infrastructure.cluster.x-k8s.io_byoclustertemplates.yaml
- bases/infrastructure.cluster.x-k8s.io_k8sinstallerconfigs.yaml
- bases/infrastructure.cluster.x-k8s.io_k8sinstallerconfigtemplates.yaml
- bases/infrastructure.cluster.x-k8s.io_bootstrapkubeconfigs.yaml
#+kubebuilder:scaffold:crdkustomizeresource

patchesStrategicMerge:
Expand All @@ -26,6 +27,7 @@ patchesStrategicMerge:
#- patches/webhook_in_byohosts.yaml
#- patches/webhook_in_k8sinstallerconfigs.yaml
#- patches/webhook_in_k8sinstallerconfigtemplates.yaml
#- patches/webhook_in_bootstrapkubeconfigs.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch

# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
Expand All @@ -38,6 +40,7 @@ patchesStrategicMerge:
#- patches/cainjection_in_byohosts.yaml
#- patches/cainjection_in_k8sinstallerconfigs.yaml
#- patches/cainjection_in_k8sinstallerconfigtemplates.yaml
#- patches/cainjection_in_bootstrapkubeconfigs.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch

# the following config is for teaching kustomize how to do kustomization for CRDs.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: bootstrapkubeconfigs.infrastructure.cluster.x-k8s.io
Loading

0 comments on commit 9e7924e

Please sign in to comment.