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:
Kubernetes Submit Queue 2017-02-16 15:49:19 -08:00 committed by GitHub
commit 2948c89433
24 changed files with 442 additions and 326 deletions

View File

@ -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",
],

View File

@ -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",

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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",

View File

@ -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
}

View File

@ -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",

View File

@ -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 != "" {

View File

@ -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",

View File

@ -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

View File

@ -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",

View File

@ -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,
},
},
},

View 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"],
)

View 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
}

View 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",
)
}
}
}
}

View File

@ -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)

View 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

View File

@ -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"],
)

View 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"],
)

View 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
}

View File

@ -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",
)
}
}
}
}

View File

@ -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
}