Merge pull request #53929 from wackxu/valuse

Automatic merge from submit-queue. 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>.

Validate usage strings when creating bootstrap tokens via kubeadm

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

fix TODO: Validate usages here so we don't allow something unsupported

**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
```
This commit is contained in:
Kubernetes Submit Queue 2017-11-18 15:31:28 -08:00 committed by GitHub
commit 5653b69bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 7 deletions

View File

@ -218,6 +218,11 @@ func RunCreateToken(out io.Writer, client clientset.Interface, token string, tok
} }
} }
// validate usages
if err := bootstrapapi.ValidateUsages(usages); err != nil {
return err
}
err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, extraGroups, description) err := tokenphase.CreateNewToken(client, token, tokenDuration, usages, extraGroups, description)
if err != nil { if err != nil {
return err return err

View File

@ -53,7 +53,11 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists
return fmt.Errorf("a token with id %q already exists", tokenID) return fmt.Errorf("a token with id %q already exists", tokenID)
} }
// Secret with this ID already exists, update it: // Secret with this ID already exists, update it:
secret.Data = encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description) tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description)
if err != nil {
return err
}
secret.Data = tokenSecretData
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil { if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Update(secret); err == nil {
return nil return nil
} }
@ -63,12 +67,17 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists
// Secret does not already exist: // Secret does not already exist:
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {
tokenSecretData, err := encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description)
if err != nil {
return err
}
secret = &v1.Secret{ secret = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: secretName, Name: secretName,
}, },
Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken), Type: v1.SecretType(bootstrapapi.SecretTypeBootstrapToken),
Data: encodeTokenSecretData(tokenID, tokenSecret, tokenDuration, usages, extraGroups, description), Data: tokenSecretData,
} }
if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil { if _, err := client.CoreV1().Secrets(metav1.NamespaceSystem).Create(secret); err == nil {
return nil return nil
@ -86,7 +95,7 @@ func UpdateOrCreateToken(client clientset.Interface, token string, failIfExists
} }
// encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret // encodeTokenSecretData takes the token discovery object and an optional duration and returns the .Data for the Secret
func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) map[string][]byte { func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration, usages []string, extraGroups []string, description string) (map[string][]byte, error) {
data := map[string][]byte{ data := map[string][]byte{
bootstrapapi.BootstrapTokenIDKey: []byte(tokenID), bootstrapapi.BootstrapTokenIDKey: []byte(tokenID),
bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret), bootstrapapi.BootstrapTokenSecretKey: []byte(tokenSecret),
@ -104,9 +113,13 @@ func encodeTokenSecretData(tokenID, tokenSecret string, duration time.Duration,
if len(description) > 0 { if len(description) > 0 {
data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description) data[bootstrapapi.BootstrapTokenDescriptionKey] = []byte(description)
} }
// validate usages
if err := bootstrapapi.ValidateUsages(usages); err != nil {
return nil, err
}
for _, usage := range usages { for _, usage := range usages {
// TODO: Validate the usage string here before
data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true") data[bootstrapapi.BootstrapTokenUsagePrefix+usage] = []byte("true")
} }
return data return data, nil
} }

View File

@ -33,7 +33,7 @@ func TestEncodeTokenSecretData(t *testing.T) {
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default {token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, t: time.Second}, // should use default
} }
for _, rt := range tests { for _, rt := range tests {
actual := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "") actual, _ := encodeTokenSecretData(rt.token.ID, rt.token.Secret, rt.t, []string{}, []string{}, "")
if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) { if !bytes.Equal(actual["token-id"], []byte(rt.token.ID)) {
t.Errorf( t.Errorf(
"failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s", "failed EncodeTokenSecretData:\n\texpected: %s\n\t actual: %s",

View File

@ -14,7 +14,10 @@ go_library(
"types.go", "types.go",
], ],
importpath = "k8s.io/kubernetes/pkg/bootstrap/api", importpath = "k8s.io/kubernetes/pkg/bootstrap/api",
deps = ["//vendor/k8s.io/api/core/v1:go_default_library"], deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
) )
filegroup( filegroup(

View File

@ -18,7 +18,9 @@ package api
import ( import (
"fmt" "fmt"
"k8s.io/apimachinery/pkg/util/sets"
"regexp" "regexp"
"strings"
) )
var bootstrapGroupRegexp = regexp.MustCompile(`\A` + BootstrapGroupPattern + `\z`) var bootstrapGroupRegexp = regexp.MustCompile(`\A` + BootstrapGroupPattern + `\z`)
@ -32,3 +34,19 @@ func ValidateBootstrapGroupName(name string) error {
} }
return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, BootstrapGroupPattern) return fmt.Errorf("bootstrap group %q is invalid (must match %s)", name, BootstrapGroupPattern)
} }
// ValidateUsages validates that the passed in string are valid usage strings for bootstrap tokens.
func ValidateUsages(usages []string) error {
usageAuthentication := strings.TrimPrefix(BootstrapTokenUsageAuthentication, BootstrapTokenUsagePrefix)
usageSigning := strings.TrimPrefix(BootstrapTokenUsageSigningKey, BootstrapTokenUsagePrefix)
invalidUsages := sets.NewString()
for _, usage := range usages {
if usage != usageAuthentication && usage != usageSigning {
invalidUsages.Insert(usage)
}
}
if len(invalidUsages) > 0 {
return fmt.Errorf("invalide bootstrap token usage string: %s, valid usage option: %s, %s", strings.Join(invalidUsages.List(), ","), usageAuthentication, usageSigning)
}
return nil
}

View File

@ -50,3 +50,27 @@ func TestValidateBootstrapGroupName(t *testing.T) {
} }
} }
} }
func TestValidateUsages(t *testing.T) {
tests := []struct {
name string
input []string
valid bool
}{
{"valid of signing", []string{"signing"}, true},
{"valid of authentication", []string{"authentication"}, true},
{"all valid", []string{"authentication", "signing"}, true},
{"single invalid", []string{"authentication", "foo"}, false},
{"all invalid", []string{"foo", "bar"}, false},
}
for _, test := range tests {
err := ValidateUsages(test.input)
if err != nil && test.valid {
t.Errorf("test %q: ValidateUsages(%v) returned unexpected error: %v", test.name, test.input, err)
}
if err == nil && !test.valid {
t.Errorf("test %q: ValidateUsages(%v) was supposed to return an error but didn't", test.name, test.input)
}
}
}