mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #41509 from luxas/kubeadm_reorder_tokens
Automatic merge from submit-queue (batch tested with PRs 38101, 41431, 39606, 41569, 41509) kubeadm: Reorder the token packages more logically **What this PR does / why we need it**: In order to be able to implement https://github.com/kubernetes/kubernetes/pull/41417, the token functionality (which now is spread across the codebase), should be in two places: a generic token functions library, which in the future _may_ [move into client-go](https://github.com/kubernetes/kubernetes/pull/41281#discussion_r101357106) in some form, and a package for the token handling against the api server. This commit has no large functional changes. ``` kubeadm: Aggregate the token functionality in sane packages. - Factor out token constants to kubeadmconstants. - Move cmd/kubeadm/app/util/{,token/}tokens.go - Use the token-id, token-secret, etc constants provided by the bootstrapapi package - Move cmd/kubeadm/app/master/tokens.go to cmd/kubeadm/app/phases/token/csv.go This refactor basically makes it possible to hook up kubeadm to the BootstrapSigner controller later on ``` **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes # **Special notes for your reviewer**: **Release note**: ```release-note NONE ``` @mikedanese @pires @errordeveloper @dmmcquay @jbeda @GheRivero
This commit is contained in:
commit
2948c89433
@ -41,6 +41,7 @@ filegroup(
|
||||
"//cmd/kubeadm/app/phases/apiconfig:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/certs:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/token:all-srcs",
|
||||
"//cmd/kubeadm/app/preflight:all-srcs",
|
||||
"//cmd/kubeadm/app/util:all-srcs",
|
||||
],
|
||||
|
@ -33,8 +33,10 @@ go_library(
|
||||
"//cmd/kubeadm/app/phases/apiconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/token:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
|
||||
"github.com/blang/semver"
|
||||
)
|
||||
@ -75,13 +76,13 @@ func setInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
|
||||
// Validate token if any, otherwise generate
|
||||
if cfg.Discovery.Token != nil {
|
||||
if cfg.Discovery.Token.ID != "" && cfg.Discovery.Token.Secret != "" {
|
||||
fmt.Printf("[init] A token has been provided, validating [%s]\n", kubeadmutil.BearerToken(cfg.Discovery.Token))
|
||||
if valid, err := kubeadmutil.ValidateToken(cfg.Discovery.Token); valid == false {
|
||||
fmt.Printf("[init] A token has been provided, validating [%s]\n", tokenutil.BearerToken(cfg.Discovery.Token))
|
||||
if valid, err := tokenutil.ValidateToken(cfg.Discovery.Token); valid == false {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[init] A token has not been provided, generating one")
|
||||
if err := kubeadmutil.GenerateToken(cfg.Discovery.Token); err != nil {
|
||||
if err := tokenutil.GenerateToken(cfg.Discovery.Token); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,10 @@ import (
|
||||
apiconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/apiconfig"
|
||||
certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
|
||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -206,7 +208,7 @@ func (i *Init) Run(out io.Writer) error {
|
||||
// TODO: It's not great to have an exception for token here, but necessary because the apiserver doesn't handle this properly in the API yet
|
||||
// but relies on files on disk for now, which is daunting.
|
||||
if i.cfg.Discovery.Token != nil {
|
||||
if err := kubemaster.CreateTokenAuthFile(kubeadmutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
|
||||
if err := tokenphase.CreateTokenAuthFile(tokenutil.BearerToken(i.cfg.Discovery.Token)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -235,7 +237,19 @@ func (i *Init) Run(out io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 4: Set up various things in the API
|
||||
// PHASE 4: Set up the bootstrap tokens
|
||||
if i.cfg.Discovery.Token != nil {
|
||||
fmt.Printf("[token-discovery] Using token: %s\n", tokenutil.BearerToken(i.cfg.Discovery.Token))
|
||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tokenphase.UpdateOrCreateToken(client, i.cfg.Discovery.Token, kubeadmconstants.DefaultTokenDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 5: Install and deploy all addons, and configure things as necessary
|
||||
|
||||
// Create the necessary ServiceAccounts
|
||||
err = apiconfigphase.CreateServiceAccounts(client)
|
||||
if err != nil {
|
||||
@ -249,17 +263,6 @@ func (i *Init) Run(out io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
if i.cfg.Discovery.Token != nil {
|
||||
fmt.Printf("[token-discovery] Using token: %s\n", kubeadmutil.BearerToken(i.cfg.Discovery.Token))
|
||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(i.cfg, client); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubeadmutil.UpdateOrCreateToken(client, i.cfg.Discovery.Token, kubeadmutil.DefaultTokenDuration); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PHASE 5: Deploy essential addons
|
||||
if err := addonsphase.CreateEssentialAddons(i.cfg, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -31,9 +31,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
tokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/token"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl"
|
||||
)
|
||||
@ -69,7 +72,7 @@ func NewCmdToken(out io.Writer, errW io.Writer) *cobra.Command {
|
||||
},
|
||||
}
|
||||
createCmd.PersistentFlags().DurationVar(&tokenDuration,
|
||||
"ttl", kubeadmutil.DefaultTokenDuration, "The duration before the token is automatically deleted.")
|
||||
"ttl", kubeadmconstants.DefaultTokenDuration, "The duration before the token is automatically deleted. 0 means 'never expires'.")
|
||||
createCmd.PersistentFlags().StringVar(
|
||||
&token, "token", "",
|
||||
"Shared secret used to secure cluster bootstrap. If none is provided, one will be generated for you.",
|
||||
@ -133,29 +136,29 @@ func RunCreateToken(out io.Writer, cmd *cobra.Command, tokenDuration time.Durati
|
||||
return err
|
||||
}
|
||||
|
||||
parsedID, parsedSecret, err := kubeadmutil.ParseToken(token)
|
||||
parsedID, parsedSecret, err := tokenutil.ParseToken(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
td := &kubeadmapi.TokenDiscovery{ID: parsedID, Secret: parsedSecret}
|
||||
|
||||
err = kubeadmutil.UpdateOrCreateToken(client, td, tokenDuration)
|
||||
err = tokenphase.UpdateOrCreateToken(client, td, tokenDuration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(out, kubeadmutil.BearerToken(td))
|
||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunGenerateToken(out io.Writer) error {
|
||||
td := &kubeadmapi.TokenDiscovery{}
|
||||
err := kubeadmutil.GenerateToken(td)
|
||||
err := tokenutil.GenerateToken(td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, kubeadmutil.BearerToken(td))
|
||||
fmt.Fprintln(out, tokenutil.BearerToken(td))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -183,31 +186,31 @@ func RunListTokens(out io.Writer, errW io.Writer, cmd *cobra.Command) error {
|
||||
w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0)
|
||||
fmt.Fprintln(w, "ID\tTOKEN\tTTL")
|
||||
for _, secret := range results.Items {
|
||||
tokenId, ok := secret.Data["token-id"]
|
||||
tokenId, ok := secret.Data[bootstrapapi.BootstrapTokenIDKey]
|
||||
if !ok {
|
||||
fmt.Fprintf(errW, "[token] bootstrap token has no token-id data: %s\n", secret.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tokenSecret, ok := secret.Data["token-secret"]
|
||||
tokenSecret, ok := secret.Data[bootstrapapi.BootstrapTokenSecretKey]
|
||||
if !ok {
|
||||
fmt.Fprintf(errW, "[token] bootstrap token has no token-secret data: %s\n", secret.Name)
|
||||
continue
|
||||
}
|
||||
token := fmt.Sprintf("%s.%s", tokenId, tokenSecret)
|
||||
td := &kubeadmapi.TokenDiscovery{ID: string(tokenId), Secret: string(tokenSecret)}
|
||||
|
||||
// Expiration time is optional, if not specified this implies the token
|
||||
// never expires.
|
||||
expires := "<never>"
|
||||
secretExpiration, ok := secret.Data["expiration"]
|
||||
secretExpiration, ok := secret.Data[bootstrapapi.BootstrapTokenExpirationKey]
|
||||
if ok {
|
||||
expireTime, err := time.Parse(time.RFC3339, string(secretExpiration))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing expiry time [%v]", err)
|
||||
return fmt.Errorf("error parsing expiration time [%v]", err)
|
||||
}
|
||||
expires = kubectl.ShortHumanDuration(expireTime.Sub(time.Now()))
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", tokenId, token, expires)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", tokenId, tokenutil.BearerToken(td), expires)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
@ -216,7 +219,7 @@ func RunListTokens(out io.Writer, errW io.Writer, cmd *cobra.Command) error {
|
||||
|
||||
// RunDeleteToken removes a bootstrap token from the server.
|
||||
func RunDeleteToken(out io.Writer, cmd *cobra.Command, tokenId string) error {
|
||||
if err := kubeadmutil.ParseTokenID(tokenId); err != nil {
|
||||
if err := tokenutil.ParseTokenID(tokenId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -225,7 +228,7 @@ func RunDeleteToken(out io.Writer, cmd *cobra.Command, tokenId string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tokenSecretName := fmt.Sprintf("%s%s", kubeadmutil.BootstrapTokenSecretPrefix, tokenId)
|
||||
tokenSecretName := fmt.Sprintf("%s%s", kubeadmconstants.BootstrapTokenSecretPrefix, tokenId)
|
||||
if err := client.Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
|
||||
return fmt.Errorf("failed to delete bootstrap token [%v]", err)
|
||||
}
|
||||
|
@ -67,4 +67,18 @@ const (
|
||||
// Minimum amount of nodes the Service subnet should allow.
|
||||
// We need at least ten, because the DNS service is always at the tenth cluster clusterIP
|
||||
MinimumAddressesInServiceSubnet = 10
|
||||
|
||||
// DefaultTokenDuration specifies the default amount of time that a bootstrap token will be valid
|
||||
DefaultTokenDuration = time.Duration(8) * time.Hour
|
||||
// BootstrapTokenSecretPrefix the the prefix that will be used for the Secrets that are created as type bootstrap.kubernetes.io/token
|
||||
BootstrapTokenSecretPrefix = "bootstrap-token-"
|
||||
|
||||
// CSVTokenBootstrapUser is currently the user the bootstrap token in the .csv file
|
||||
// TODO: This should change to something more official and supported
|
||||
// TODO: Prefix with kubeadm prefix
|
||||
CSVTokenBootstrapUser = "kubeadm-node-csr"
|
||||
// CSVTokenBootstrapGroup specifies the group the tokens in the .csv file will belong to
|
||||
CSVTokenBootstrapGroup = "kubeadm:kubelet-bootstrap"
|
||||
// The file name of the tokens file that can be used for bootstrapping
|
||||
CSVTokenFileName = "tokens.csv"
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ go_library(
|
||||
"//cmd/kubeadm/app/discovery/https:go_default_library",
|
||||
"//cmd/kubeadm/app/discovery/token:go_default_library",
|
||||
"//cmd/kubeadm/app/node:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//vendor:github.com/spf13/pflag",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd",
|
||||
"//vendor:k8s.io/client-go/tools/clientcmd/api",
|
||||
|
@ -25,7 +25,7 @@ import (
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
)
|
||||
|
||||
// For identifies and executes the desired discovery mechanism.
|
||||
@ -65,7 +65,7 @@ func runHTTPSDiscovery(hd *kubeadmapi.HTTPSDiscovery) (*clientcmdapi.Config, err
|
||||
|
||||
// runTokenDiscovery executes token-based discovery.
|
||||
func runTokenDiscovery(td *kubeadmapi.TokenDiscovery) (*clientcmdapi.Config, error) {
|
||||
if valid, err := kubeadmutil.ValidateToken(td); valid == false {
|
||||
if valid, err := tokenutil.ValidateToken(td); valid == false {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ go_library(
|
||||
"manifests.go",
|
||||
"selfhosted.go",
|
||||
"templates.go",
|
||||
"tokens.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
@ -31,7 +30,6 @@ go_library(
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/intstr",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/kubernetes",
|
||||
"//vendor:k8s.io/client-go/pkg/api",
|
||||
|
@ -392,7 +392,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, selfHosted
|
||||
"--service-account-private-key-file="+getCertFilePath(kubeadmconstants.ServiceAccountPrivateKeyName),
|
||||
"--cluster-signing-cert-file="+getCertFilePath(kubeadmconstants.CACertName),
|
||||
"--cluster-signing-key-file="+getCertFilePath(kubeadmconstants.CAKeyName),
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group="+KubeletBootstrapGroup,
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group="+kubeadmconstants.CSVTokenBootstrapGroup,
|
||||
)
|
||||
|
||||
if cfg.CloudProvider != "" {
|
||||
|
@ -19,7 +19,7 @@ go_library(
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/kubelet/util/csr:go_default_library",
|
||||
"//vendor:github.com/square/go-jose",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
)
|
||||
|
||||
// retryTimeout between the subsequent attempts to connect
|
||||
@ -62,7 +62,7 @@ func EstablishMasterConnection(c *kubeadmapi.TokenDiscovery, clusterInfo *kubead
|
||||
var once sync.Once
|
||||
var wg sync.WaitGroup
|
||||
for _, endpoint := range endpoints {
|
||||
ac, err := createClients(caCert, endpoint, kubeadmutil.BearerToken(c), nodeName)
|
||||
ac, err := createClients(caCert, endpoint, tokenutil.BearerToken(c), nodeName)
|
||||
if err != nil {
|
||||
fmt.Printf("[bootstrap] Warning: %s. Skipping endpoint %s\n", err, endpoint)
|
||||
continue
|
||||
|
@ -16,7 +16,6 @@ go_library(
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/master:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/master"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -117,7 +116,7 @@ func CreateClusterRoleBindings(clientset *clientset.Clientset) error {
|
||||
Subjects: []rbac.Subject{
|
||||
{
|
||||
Kind: "Group",
|
||||
Name: master.KubeletBootstrapGroup,
|
||||
Name: kubeadmconstants.CSVTokenBootstrapGroup,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
54
cmd/kubeadm/app/phases/token/BUILD
Normal file
54
cmd/kubeadm/app/phases/token/BUILD
Normal file
@ -0,0 +1,54 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"bootstrap_test.go",
|
||||
"csv_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bootstrap.go",
|
||||
"csv.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/util/token:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
|
||||
"//vendor:k8s.io/client-go/kubernetes",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
99
cmd/kubeadm/app/phases/token/bootstrap.go
Normal file
99
cmd/kubeadm/app/phases/token/bootstrap.go
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
tokenCreateRetries = 5
|
||||
)
|
||||
|
||||
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does
|
||||
// not already exist.
|
||||
func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration) error {
|
||||
// Let's make sure the token is valid
|
||||
if valid, err := tokenutil.ValidateToken(d); !valid {
|
||||
return err
|
||||
}
|
||||
secretName := fmt.Sprintf("%s%s", kubeadmconstants.BootstrapTokenSecretPrefix, d.ID)
|
||||
var lastErr error
|
||||
for i := 0; i < tokenCreateRetries; i++ {
|
||||
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
// Secret with this ID already exists, update it:
|
||||
secret.Data = encodeTokenSecretData(d, tokenDuration)
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Secret does not already exist:
|
||||
if apierrors.IsNotFound(err) {
|
||||
secret = &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
||||
Data: encodeTokenSecretData(d, tokenDuration),
|
||||
}
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"unable to create bootstrap token after %d attempts [%v]",
|
||||
tokenCreateRetries,
|
||||
lastErr,
|
||||
)
|
||||
}
|
||||
|
||||
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
|
||||
func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration) map[string][]byte {
|
||||
data := map[string][]byte{
|
||||
bootstrapapi.BootstrapTokenIDKey: []byte(d.ID),
|
||||
bootstrapapi.BootstrapTokenSecretKey: []byte(d.Secret),
|
||||
bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"),
|
||||
}
|
||||
|
||||
if duration > 0 {
|
||||
// Get the current time, add the specified duration, and format it accordingly
|
||||
durationString := time.Now().Add(duration).Format(time.RFC3339)
|
||||
data[bootstrapapi.BootstrapTokenExpirationKey] = []byte(durationString)
|
||||
}
|
||||
return data
|
||||
}
|
59
cmd/kubeadm/app/phases/token/bootstrap_test.go
Normal file
59
cmd/kubeadm/app/phases/token/bootstrap_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
|
||||
func TestEncodeTokenSecretData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token *kubeadmapi.TokenDiscovery
|
||||
t time.Duration
|
||||
}{
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}}, // should use default
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := encodeTokenSecretData(rt.token, rt.t)
|
||||
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.ID,
|
||||
actual["token-id"],
|
||||
)
|
||||
}
|
||||
if !bytes.Equal(actual["token-secret"], []byte(rt.token.Secret)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.Secret,
|
||||
actual["token-secret"],
|
||||
)
|
||||
}
|
||||
if rt.t > 0 {
|
||||
if actual["expiration"] == nil {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData, duration was not added to time",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package master
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -24,22 +24,17 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: prefix with kubeadm prefix
|
||||
KubeletBootstrapUser = "kubeadm-node-csr"
|
||||
|
||||
KubeletBootstrapGroup = "kubeadm:kubelet-bootstrap"
|
||||
)
|
||||
|
||||
// CreateTokenAuthFile creates the CSV file that can be used for allowing users with tokens access to the API Server
|
||||
func CreateTokenAuthFile(bt string) error {
|
||||
tokenAuthFilePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, "tokens.csv")
|
||||
tokenAuthFilePath := path.Join(kubeadmapi.GlobalEnvParams.HostPKIPath, kubeadmconstants.CSVTokenFileName)
|
||||
if err := os.MkdirAll(kubeadmapi.GlobalEnvParams.HostPKIPath, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q [%v]", kubeadmapi.GlobalEnvParams.HostPKIPath, err)
|
||||
}
|
||||
serialized := []byte(fmt.Sprintf("%s,%s,%s,%s\n", bt, KubeletBootstrapUser, uuid.NewUUID(), KubeletBootstrapGroup))
|
||||
serialized := []byte(fmt.Sprintf("%s,%s,%s,%s\n", bt, kubeadmconstants.CSVTokenBootstrapUser, uuid.NewUUID(), kubeadmconstants.CSVTokenBootstrapGroup))
|
||||
// DumpReaderToFile create a file with mode 0600
|
||||
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil {
|
||||
return fmt.Errorf("failed to save token auth file (%q) [%v]", tokenAuthFilePath, err)
|
17
cmd/kubeadm/app/phases/token/csv_test.go
Normal file
17
cmd/kubeadm/app/phases/token/csv_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 token
|
@ -13,20 +13,10 @@ go_library(
|
||||
srcs = [
|
||||
"error.go",
|
||||
"template.go",
|
||||
"tokens.go",
|
||||
"version.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
||||
"//pkg/bootstrap/api:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/client-go/kubernetes",
|
||||
"//vendor:k8s.io/client-go/pkg/api/v1",
|
||||
],
|
||||
deps = ["//cmd/kubeadm/app/preflight:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
@ -34,15 +24,11 @@ go_test(
|
||||
srcs = [
|
||||
"error_test.go",
|
||||
"template_test.go",
|
||||
"tokens_test.go",
|
||||
"version_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
||||
],
|
||||
deps = ["//cmd/kubeadm/app/preflight:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
@ -54,6 +40,9 @@ filegroup(
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/kubeadm/app/util/token:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
37
cmd/kubeadm/app/util/token/BUILD
Normal file
37
cmd/kubeadm/app/util/token/BUILD
Normal file
@ -0,0 +1,37 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["tokens_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["tokens.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
100
cmd/kubeadm/app/util/token/tokens.go
Normal file
100
cmd/kubeadm/app/util/token/tokens.go
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 token
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenIDBytes = 3
|
||||
TokenSecretBytes = 8
|
||||
)
|
||||
|
||||
var (
|
||||
tokenIDRegexpString = "^([a-z0-9]{6})$"
|
||||
tokenIDRegexp = regexp.MustCompile(tokenIDRegexpString)
|
||||
tokenRegexpString = "^([a-z0-9]{6})\\:([a-z0-9]{16})$"
|
||||
tokenRegexp = regexp.MustCompile(tokenRegexpString)
|
||||
)
|
||||
|
||||
func randBytes(length int) (string, error) {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// GenerateToken generates a new token with a token ID that is valid as a
|
||||
// Kubernetes DNS label.
|
||||
// For more info, see kubernetes/pkg/util/validation/validation.go.
|
||||
func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
|
||||
tokenID, err := randBytes(TokenIDBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := randBytes(TokenSecretBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.ID = strings.ToLower(tokenID)
|
||||
d.Secret = strings.ToLower(token)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTokenID tries and parse a valid token ID from a string.
|
||||
// An error is returned in case of failure.
|
||||
func ParseTokenID(s string) error {
|
||||
if !tokenIDRegexp.MatchString(s) {
|
||||
return fmt.Errorf("token ID [%q] was not of form [%q]", s, tokenIDRegexpString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseToken tries and parse a valid token from a string.
|
||||
// A token ID and token secret are returned in case of success, an error otherwise.
|
||||
func ParseToken(s string) (string, string, error) {
|
||||
split := tokenRegexp.FindStringSubmatch(s)
|
||||
if len(split) != 3 {
|
||||
return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
|
||||
}
|
||||
return split[1], split[2], nil
|
||||
}
|
||||
|
||||
// BearerToken returns a string representation of the passed token.
|
||||
func BearerToken(d *kubeadmapi.TokenDiscovery) string {
|
||||
return fmt.Sprintf("%s:%s", d.ID, d.Secret)
|
||||
}
|
||||
|
||||
// ValidateToken validates whether a token is well-formed.
|
||||
// In case it's not, the corresponding error is returned as well.
|
||||
func ValidateToken(d *kubeadmapi.TokenDiscovery) (bool, error) {
|
||||
if _, _, err := ParseToken(d.ID + ":" + d.Secret); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -14,12 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
)
|
||||
@ -151,28 +149,6 @@ func TestRandBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoveryPort(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token *kubeadmapi.TokenDiscovery
|
||||
expected int32
|
||||
}{
|
||||
{token: &kubeadmapi.TokenDiscovery{}, expected: 9898}, // should use default
|
||||
{token: &kubeadmapi.TokenDiscovery{Addresses: []string{"foobar:1234"}}, expected: 1234},
|
||||
{token: &kubeadmapi.TokenDiscovery{Addresses: []string{"doesnothaveport"}}, expected: 9898}, // should use default
|
||||
{token: &kubeadmapi.TokenDiscovery{Addresses: []string{"foorbar:abcd"}}, expected: 9898}, // since abcd isn't an int, should use default
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := DiscoveryPort(rt.token)
|
||||
if actual != rt.expected {
|
||||
t.Errorf(
|
||||
"failed DiscoveryPort:\n\texpected: %d\n\t actual: %d",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerToken(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token *kubeadmapi.TokenDiscovery
|
||||
@ -191,37 +167,3 @@ func TestBearerToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeTokenSecretData(t *testing.T) {
|
||||
var tests = []struct {
|
||||
token *kubeadmapi.TokenDiscovery
|
||||
t time.Duration
|
||||
}{
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}}, // should use default
|
||||
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := encodeTokenSecretData(rt.token, rt.t)
|
||||
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.ID,
|
||||
actual["token-id"],
|
||||
)
|
||||
}
|
||||
if !bytes.Equal(actual["token-secret"], []byte(rt.token.Secret)) {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",
|
||||
rt.token.Secret,
|
||||
actual["token-secret"],
|
||||
)
|
||||
}
|
||||
if rt.t > 0 {
|
||||
if actual["expiration"] == nil {
|
||||
t.Errorf(
|
||||
"failed EncodeTokenSecretData, duration was not added to time",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
v1 "k8s.io/client-go/pkg/api/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenIDBytes = 3
|
||||
TokenSecretBytes = 8
|
||||
BootstrapTokenSecretPrefix = "bootstrap-token-"
|
||||
DefaultTokenDuration = time.Duration(8) * time.Hour
|
||||
tokenCreateRetries = 5
|
||||
)
|
||||
|
||||
var (
|
||||
tokenIDRegexpString = "^([a-z0-9]{6})$"
|
||||
tokenIDRegexp = regexp.MustCompile(tokenIDRegexpString)
|
||||
tokenRegexpString = "^([a-z0-9]{6})\\:([a-z0-9]{16})$"
|
||||
tokenRegexp = regexp.MustCompile(tokenRegexpString)
|
||||
)
|
||||
|
||||
func randBytes(length int) (string, error) {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
// GenerateToken generates a new token with a token ID that is valid as a
|
||||
// Kubernetes DNS label.
|
||||
// For more info, see kubernetes/pkg/util/validation/validation.go.
|
||||
func GenerateToken(d *kubeadmapi.TokenDiscovery) error {
|
||||
tokenID, err := randBytes(TokenIDBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := randBytes(TokenSecretBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.ID = strings.ToLower(tokenID)
|
||||
d.Secret = strings.ToLower(token)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTokenID tries and parse a valid token ID from a string.
|
||||
// An error is returned in case of failure.
|
||||
func ParseTokenID(s string) error {
|
||||
if !tokenIDRegexp.MatchString(s) {
|
||||
return fmt.Errorf("token ID [%q] was not of form [%q]", s, tokenIDRegexpString)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// ParseToken tries and parse a valid token from a string.
|
||||
// A token ID and token secret are returned in case of success, an error otherwise.
|
||||
func ParseToken(s string) (string, string, error) {
|
||||
split := tokenRegexp.FindStringSubmatch(s)
|
||||
if len(split) != 3 {
|
||||
return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, tokenRegexpString)
|
||||
}
|
||||
return split[1], split[2], nil
|
||||
|
||||
}
|
||||
|
||||
// BearerToken returns a string representation of the passed token.
|
||||
func BearerToken(d *kubeadmapi.TokenDiscovery) string {
|
||||
return fmt.Sprintf("%s:%s", d.ID, d.Secret)
|
||||
}
|
||||
|
||||
// ValidateToken validates whether a token is well-formed.
|
||||
// In case it's not, the corresponding error is returned as well.
|
||||
func ValidateToken(d *kubeadmapi.TokenDiscovery) (bool, error) {
|
||||
if _, _, err := ParseToken(d.ID + ":" + d.Secret); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DiscoveryPort(d *kubeadmapi.TokenDiscovery) int32 {
|
||||
if len(d.Addresses) == 0 {
|
||||
return kubeadmapiext.DefaultDiscoveryBindPort
|
||||
}
|
||||
split := strings.Split(d.Addresses[0], ":")
|
||||
if len(split) == 1 {
|
||||
return kubeadmapiext.DefaultDiscoveryBindPort
|
||||
}
|
||||
if i, err := strconv.Atoi(split[1]); err == nil {
|
||||
return int32(i)
|
||||
}
|
||||
return kubeadmapiext.DefaultDiscoveryBindPort
|
||||
}
|
||||
|
||||
// UpdateOrCreateToken attempts to update a token with the given ID, or create if it does
|
||||
// not already exist.
|
||||
func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscovery, tokenDuration time.Duration) error {
|
||||
// Let's make sure
|
||||
if valid, err := ValidateToken(d); !valid {
|
||||
return err
|
||||
}
|
||||
secretName := fmt.Sprintf("%s%s", BootstrapTokenSecretPrefix, d.ID)
|
||||
var lastErr error
|
||||
for i := 0; i < tokenCreateRetries; i++ {
|
||||
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
// Secret with this ID already exists, update it:
|
||||
secret.Data = encodeTokenSecretData(d, tokenDuration)
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Secret does not already exist:
|
||||
if apierrors.IsNotFound(err) {
|
||||
secret = &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
},
|
||||
// TODO(jbeda): convert kubeadm to client-go
|
||||
// https://github.com/kubernetes/kubeadm/issues/52
|
||||
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
|
||||
Data: encodeTokenSecretData(d, tokenDuration),
|
||||
}
|
||||
if _, err := client.Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"unable to create bootstrap token after %d attempts [%v]",
|
||||
tokenCreateRetries,
|
||||
lastErr,
|
||||
)
|
||||
}
|
||||
|
||||
func encodeTokenSecretData(d *kubeadmapi.TokenDiscovery, duration time.Duration) map[string][]byte {
|
||||
var (
|
||||
data = map[string][]byte{}
|
||||
)
|
||||
|
||||
data["token-id"] = []byte(d.ID)
|
||||
data["token-secret"] = []byte(d.Secret)
|
||||
|
||||
data["usage-bootstrap-signing"] = []byte("true")
|
||||
|
||||
if duration > 0 {
|
||||
t := time.Now()
|
||||
t = t.Add(duration)
|
||||
data["expiration"] = []byte(t.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
Loading…
Reference in New Issue
Block a user