Merge pull request #64408 from luxas/kubeadm_refactor_bt

Automatic merge from submit-queue (batch tested with PRs 64057, 63223, 64346, 64562, 64408). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

kubeadm: Refactor the Bootstrap Tokens usage in the API types

**What this PR does / why we need it**:
This PR:
 - Moves some common, generic Bootstrap Token helpers and constants from `k8s.io/kubernetes/cmd/kubeadm/app/util/token` to `k8s.io/client-go/tools/bootstrap/token/`
 - Breaks out the top-level Bootstrap Token fields to a dedicated `BootstrapToken` struct with helper functions.
 - Instead of representing the Bootstrap Token as a plain `string`, there is now a wrapper struct `BootstrapTokenString` that can marshal/unmarshal correctly and supports validation on create, and splitting up the full token in the ID/Secret parts automatically.
 - Makes kubeadm support multiple Bootstrap Tokens automatically by supporting a slice of `BootstrapToken` in the `MasterConfiguration` API object
 - Consolidates the place for kubeadm to create token-related flags in an `options` package
 - Supports automatic conversion from the `v1alpha1` to `v1alpha2` API
 - Adds support to set token expiration directly instead of setting a TTL (Expiration and TTL are mutually exclusive)
 - Removes the old `TokenDiscovery` struct we're not using anymore inside of kubeadm

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Related to https://github.com/kubernetes/community/pull/2131

**Special notes for your reviewer**:
This is work in progress. Please only review the first two commits for now.
I will work on splitting up this PR in smaller chunks.
I will also write unit tests tomorrow.

**Release note**:

```release-note
[action required] kubeadm: The Token-related fields in the `MasterConfiguration` object have now been refactored. Instead of the top-level `.Token`, `.TokenTTL`, `.TokenUsages`, `.TokenGroups` fields, there is now a `BootstrapTokens` slice of `BootstrapToken` objects that support the same features under the `.Token`, `.TTL`, `.Usages`, `.Groups` fields.
```
@kubernetes/sig-cluster-lifecycle-pr-reviews @mattmoyer @liztio

Kubernetes-commit: c7b71ebca95d9afb5c4adbadf6cde09a0988d5a7
This commit is contained in:
Kubernetes Publisher 2018-06-02 02:10:18 -07:00
commit 787033cb72
6 changed files with 294 additions and 64 deletions

102
Godeps/Godeps.json generated
View File

@ -1,6 +1,6 @@
{ {
"ImportPath": "k8s.io/client-go", "ImportPath": "k8s.io/client-go",
"GoVersion": "go1.10", "GoVersion": "go1.9",
"GodepVersion": "v80", "GodepVersion": "v80",
"Packages": [ "Packages": [
"./..." "./..."
@ -388,203 +388,203 @@
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/equality", "ImportPath": "k8s.io/apimachinery/pkg/api/equality",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/errors", "ImportPath": "k8s.io/apimachinery/pkg/api/errors",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/meta", "ImportPath": "k8s.io/apimachinery/pkg/api/meta",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/resource", "ImportPath": "k8s.io/apimachinery/pkg/api/resource",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/testing", "ImportPath": "k8s.io/apimachinery/pkg/api/testing",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer", "ImportPath": "k8s.io/apimachinery/pkg/api/testing/fuzzer",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/api/testing/roundtrip", "ImportPath": "k8s.io/apimachinery/pkg/api/testing/roundtrip",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1", "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion", "ImportPath": "k8s.io/apimachinery/pkg/conversion",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/fields", "ImportPath": "k8s.io/apimachinery/pkg/fields",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/labels", "ImportPath": "k8s.io/apimachinery/pkg/labels",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime", "ImportPath": "k8s.io/apimachinery/pkg/runtime",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema", "ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning", "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/selection", "ImportPath": "k8s.io/apimachinery/pkg/selection",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/types", "ImportPath": "k8s.io/apimachinery/pkg/types",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/cache", "ImportPath": "k8s.io/apimachinery/pkg/util/cache",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/clock", "ImportPath": "k8s.io/apimachinery/pkg/util/clock",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/diff", "ImportPath": "k8s.io/apimachinery/pkg/util/diff",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/errors", "ImportPath": "k8s.io/apimachinery/pkg/util/errors",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/framer", "ImportPath": "k8s.io/apimachinery/pkg/util/framer",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream", "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy", "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr", "ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/json", "ImportPath": "k8s.io/apimachinery/pkg/util/json",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch", "ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/net", "ImportPath": "k8s.io/apimachinery/pkg/util/net",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand", "ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime", "ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/sets", "ImportPath": "k8s.io/apimachinery/pkg/util/sets",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch", "ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/validation", "ImportPath": "k8s.io/apimachinery/pkg/util/validation",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field", "ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/wait", "ImportPath": "k8s.io/apimachinery/pkg/util/wait",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml", "ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/version", "ImportPath": "k8s.io/apimachinery/pkg/version",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/pkg/watch", "ImportPath": "k8s.io/apimachinery/pkg/watch",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json", "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil", "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
"Rev": "5204d3828432611e41488a8042bc1d16a7c12e05" "Rev": "1e6ce0f30d47d3c48634eabcc5b25a594654e7a5"
}, },
{ {
"ImportPath": "k8s.io/kube-openapi/pkg/util/proto", "ImportPath": "k8s.io/kube-openapi/pkg/util/proto",

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package api (pkg/bootstrap/token/api) contains constants and types needed for // Package api (k8s.io/client-go/tools/bootstrap/token/api) contains constants and types needed for
// bootstrap tokens as maintained by the BootstrapSigner and TokenCleaner // bootstrap tokens as maintained by the BootstrapSigner and TokenCleaner
// controllers (in pkg/controller/bootstrap) // controllers (in k8s.io/kubernetes/pkg/controller/bootstrap)
package api // import "k8s.io/client-go/tools/bootstrap/token/api" package api // import "k8s.io/client-go/tools/bootstrap/token/api"

View File

@ -86,14 +86,26 @@ const (
// authenticate as. The full username given is "system:bootstrap:<token-id>". // authenticate as. The full username given is "system:bootstrap:<token-id>".
BootstrapUserPrefix = "system:bootstrap:" BootstrapUserPrefix = "system:bootstrap:"
// BootstrapGroupPattern is the valid regex pattern that all groups
// assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match.
// See also ValidateBootstrapGroupName().
BootstrapGroupPattern = "system:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]"
// BootstrapDefaultGroup is the default group for bootstrapping bearer // BootstrapDefaultGroup is the default group for bootstrapping bearer
// tokens (in addition to any groups from BootstrapTokenExtraGroupsKey). // tokens (in addition to any groups from BootstrapTokenExtraGroupsKey).
BootstrapDefaultGroup = "system:bootstrappers" BootstrapDefaultGroup = "system:bootstrappers"
// BootstrapGroupPattern is the valid regex pattern that all groups
// assigned to a bootstrap token by BootstrapTokenExtraGroupsKey must match.
// See also util.ValidateBootstrapGroupName()
BootstrapGroupPattern = `\Asystem:bootstrappers:[a-z0-9:-]{0,255}[a-z0-9]\z`
// BootstrapTokenPattern defines the {id}.{secret} regular expression pattern
BootstrapTokenPattern = `\A([a-z0-9]{6})\.([a-z0-9]{16})\z`
// BootstrapTokenIDPattern defines token's id regular expression pattern
BootstrapTokenIDPattern = `\A([a-z0-9]{6})\z`
// BootstrapTokenIDBytes defines the number of bytes used for the Bootstrap Token's ID field
BootstrapTokenIDBytes = 6
// BootstrapTokenSecretBytes defines the number of bytes used the Bootstrap Token's Secret field
BootstrapTokenSecretBytes = 16
) )
// KnownTokenUsages specifies the known functions a token will get. // KnownTokenUsages specifies the known functions a token will get.

View File

@ -17,20 +17,101 @@ limitations under the License.
package util package util
import ( import (
"bufio"
"crypto/rand"
"fmt" "fmt"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/bootstrap/token/api"
"regexp" "regexp"
"strings" "strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/bootstrap/token/api"
) )
var bootstrapGroupRegexp = regexp.MustCompile(`\A` + api.BootstrapGroupPattern + `\z`) // validBootstrapTokenChars defines the characters a bootstrap token can consist of
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
var (
// BootstrapTokenRegexp is a compiled regular expression of TokenRegexpString
BootstrapTokenRegexp = regexp.MustCompile(api.BootstrapTokenPattern)
// BootstrapTokenIDRegexp is a compiled regular expression of TokenIDRegexpString
BootstrapTokenIDRegexp = regexp.MustCompile(api.BootstrapTokenIDPattern)
// BootstrapGroupRegexp is a compiled regular expression of BootstrapGroupPattern
BootstrapGroupRegexp = regexp.MustCompile(api.BootstrapGroupPattern)
)
// GenerateBootstrapToken generates a new, random Bootstrap Token.
func GenerateBootstrapToken() (string, error) {
tokenID, err := randBytes(api.BootstrapTokenIDBytes)
if err != nil {
return "", err
}
tokenSecret, err := randBytes(api.BootstrapTokenSecretBytes)
if err != nil {
return "", err
}
return TokenFromIDAndSecret(tokenID, tokenSecret), nil
}
// randBytes returns a random string consisting of the characters in
// validBootstrapTokenChars, with the length customized by the parameter
func randBytes(length int) (string, error) {
// len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide
// the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we
// read that are >= 252 so the bytes we evenly divide the character set.
const maxByteValue = 252
var (
b byte
err error
token = make([]byte, length)
)
reader := bufio.NewReaderSize(rand.Reader, length*2)
for i := range token {
for {
if b, err = reader.ReadByte(); err != nil {
return "", err
}
if b < maxByteValue {
break
}
}
token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)]
}
return string(token), nil
}
// TokenFromIDAndSecret returns the full token which is of the form "{id}.{secret}"
func TokenFromIDAndSecret(id, secret string) string {
return fmt.Sprintf("%s.%s", id, secret)
}
// IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token and
// in other words satisfies the BootstrapTokenRegexp
func IsValidBootstrapToken(token string) bool {
return BootstrapTokenRegexp.MatchString(token)
}
// IsValidBootstrapTokenID returns whether the given string is valid as a Bootstrap Token ID and
// in other words satisfies the BootstrapTokenIDRegexp
func IsValidBootstrapTokenID(tokenID string) bool {
return BootstrapTokenIDRegexp.MatchString(tokenID)
}
// BootstrapTokenSecretName returns the expected name for the Secret storing the
// Bootstrap Token in the Kubernetes API.
func BootstrapTokenSecretName(tokenID string) string {
return fmt.Sprintf("%s%s", api.BootstrapTokenSecretPrefix, tokenID)
}
// ValidateBootstrapGroupName checks if the provided group name is a valid // ValidateBootstrapGroupName checks if the provided group name is a valid
// bootstrap group name. Returns nil if valid or a validation error if invalid. // bootstrap group name. Returns nil if valid or a validation error if invalid.
// TODO(mattmoyer): this validation should migrate out to client-go (see https://github.com/kubernetes/client-go/issues/114)
func ValidateBootstrapGroupName(name string) error { func ValidateBootstrapGroupName(name string) error {
if bootstrapGroupRegexp.Match([]byte(name)) { if BootstrapGroupRegexp.Match([]byte(name)) {
return nil return nil
} }
return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, api.BootstrapGroupPattern) return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, api.BootstrapGroupPattern)
@ -46,7 +127,7 @@ func ValidateUsages(usages []string) error {
} }
} }
if len(invalidUsages) > 0 { if len(invalidUsages) > 0 {
return fmt.Errorf("invalide bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ",")) return fmt.Errorf("invalid bootstrap token usage string: %s, valid usage options: %s", strings.Join(invalidUsages.List(), ","), strings.Join(api.KnownTokenUsages, ","))
} }
return nil return nil
} }

View File

@ -21,6 +21,143 @@ import (
"testing" "testing"
) )
func TestGenerateBootstrapToken(t *testing.T) {
token, err := GenerateBootstrapToken()
if err != nil {
t.Fatalf("GenerateBootstrapToken returned an unexpected error: %+v", err)
}
if !IsValidBootstrapToken(token) {
t.Errorf("GenerateBootstrapToken didn't generate a valid token: %q", token)
}
}
func TestRandBytes(t *testing.T) {
var randTest = []int{
0,
1,
2,
3,
100,
}
for _, rt := range randTest {
actual, err := randBytes(rt)
if err != nil {
t.Errorf("failed randBytes: %v", err)
}
if len(actual) != rt {
t.Errorf("failed randBytes:\n\texpected: %d\n\t actual: %d\n", rt, len(actual))
}
}
}
func TestTokenFromIDAndSecret(t *testing.T) {
var tests = []struct {
id string
secret string
expected string
}{
{"foo", "bar", "foo.bar"}, // should use default
{"abcdef", "abcdef0123456789", "abcdef.abcdef0123456789"},
{"h", "b", "h.b"},
}
for _, rt := range tests {
actual := TokenFromIDAndSecret(rt.id, rt.secret)
if actual != rt.expected {
t.Errorf(
"failed TokenFromIDAndSecret:\n\texpected: %s\n\t actual: %s",
rt.expected,
actual,
)
}
}
}
func TestIsValidBootstrapToken(t *testing.T) {
var tests = []struct {
token string
expected bool
}{
{token: "", expected: false},
{token: ".", expected: false},
{token: "1234567890123456789012", expected: false}, // invalid parcel size
{token: "12345.1234567890123456", expected: false}, // invalid parcel size
{token: ".1234567890123456", expected: false}, // invalid parcel size
{token: "123456.", expected: false}, // invalid parcel size
{token: "123456:1234567890.123456", expected: false}, // invalid separation
{token: "abcdef:1234567890123456", expected: false}, // invalid separation
{token: "Abcdef.1234567890123456", expected: false}, // invalid token id
{token: "123456.AABBCCDDEEFFGGHH", expected: false}, // invalid token secret
{token: "123456.AABBCCD-EEFFGGHH", expected: false}, // invalid character
{token: "abc*ef.1234567890123456", expected: false}, // invalid character
{token: "abcdef.1234567890123456", expected: true},
{token: "123456.aabbccddeeffgghh", expected: true},
{token: "ABCDEF.abcdef0123456789", expected: false},
{token: "abcdef.abcdef0123456789", expected: true},
{token: "123456.1234560123456789", expected: true},
}
for _, rt := range tests {
actual := IsValidBootstrapToken(rt.token)
if actual != rt.expected {
t.Errorf(
"failed IsValidBootstrapToken for the token %q\n\texpected: %t\n\t actual: %t",
rt.token,
rt.expected,
actual,
)
}
}
}
func TestIsValidBootstrapTokenID(t *testing.T) {
var tests = []struct {
tokenID string
expected bool
}{
{tokenID: "", expected: false},
{tokenID: "1234567890123456789012", expected: false},
{tokenID: "12345", expected: false},
{tokenID: "Abcdef", expected: false},
{tokenID: "ABCDEF", expected: false},
{tokenID: "abcdef.", expected: false},
{tokenID: "abcdef", expected: true},
{tokenID: "123456", expected: true},
}
for _, rt := range tests {
actual := IsValidBootstrapTokenID(rt.tokenID)
if actual != rt.expected {
t.Errorf(
"failed IsValidBootstrapTokenID for the token %q\n\texpected: %t\n\t actual: %t",
rt.tokenID,
rt.expected,
actual,
)
}
}
}
func TestBootstrapTokenSecretName(t *testing.T) {
var tests = []struct {
tokenID string
expected string
}{
{"foo", "bootstrap-token-foo"},
{"bar", "bootstrap-token-bar"},
{"", "bootstrap-token-"},
{"abcdef", "bootstrap-token-abcdef"},
}
for _, rt := range tests {
actual := BootstrapTokenSecretName(rt.tokenID)
if actual != rt.expected {
t.Errorf(
"failed BootstrapTokenSecretName:\n\texpected: %s\n\t actual: %s",
rt.expected,
actual,
)
}
}
}
func TestValidateBootstrapGroupName(t *testing.T) { func TestValidateBootstrapGroupName(t *testing.T) {
tests := []struct { tests := []struct {
name string name string