mirror of https://github.com/k3s-io/k3s
Merge pull request #41754 from jbeda/bootstrap-secret-name
Automatic merge from submit-queue (batch tested with PRs 41709, 41685, 41754, 41759, 37237) Ignore Bootstrap Token secrets that don't use predictable names.pull/6/head
commit
41bee6de16
|
@ -227,7 +227,7 @@ func RunDeleteToken(out io.Writer, cmd *cobra.Command, tokenId string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenSecretName := fmt.Sprintf("%s%s", kubeadmconstants.BootstrapTokenSecretPrefix, tokenId)
|
tokenSecretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, tokenId)
|
||||||
if err := client.Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
|
if err := client.Secrets(metav1.NamespaceSystem).Delete(tokenSecretName, nil); err != nil {
|
||||||
return fmt.Errorf("failed to delete bootstrap token [%v]", err)
|
return fmt.Errorf("failed to delete bootstrap token [%v]", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,6 @@ const (
|
||||||
|
|
||||||
// DefaultTokenDuration specifies the default amount of time that a bootstrap token will be valid
|
// DefaultTokenDuration specifies the default amount of time that a bootstrap token will be valid
|
||||||
DefaultTokenDuration = time.Duration(8) * time.Hour
|
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
|
// CSVTokenBootstrapUser is currently the user the bootstrap token in the .csv file
|
||||||
// TODO: This should change to something more official and supported
|
// TODO: This should change to something more official and supported
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
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"
|
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
|
||||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
)
|
)
|
||||||
|
@ -41,7 +40,7 @@ func UpdateOrCreateToken(client *clientset.Clientset, d *kubeadmapi.TokenDiscove
|
||||||
if valid, err := tokenutil.ValidateToken(d); !valid {
|
if valid, err := tokenutil.ValidateToken(d); !valid {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
secretName := fmt.Sprintf("%s%s", kubeadmconstants.BootstrapTokenSecretPrefix, d.ID)
|
secretName := fmt.Sprintf("%s%s", bootstrapapi.BootstrapTokenSecretPrefix, d.ID)
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for i := 0; i < tokenCreateRetries; i++ {
|
for i := 0; i < tokenCreateRetries; i++ {
|
||||||
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
secret, err := client.Secrets(metav1.NamespaceSystem).Get(secretName, metav1.GetOptions{})
|
||||||
|
|
|
@ -21,6 +21,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// BootstrapTokenSecretPrefix is the prefix for bootstrap token names.
|
||||||
|
// Bootstrap tokens secrets must be named in the form
|
||||||
|
// `bootstrap-token-<token-id>`. This is the prefix to be used before the
|
||||||
|
// token ID.
|
||||||
|
BootstrapTokenSecretPrefix = "bootstrap-token-"
|
||||||
|
|
||||||
// SecretTypeBootstrapToken is used during the automated bootstrap process (first
|
// SecretTypeBootstrapToken is used during the automated bootstrap process (first
|
||||||
// implemented by kubeadm). It stores tokens that are used to sign well known
|
// implemented by kubeadm). It stores tokens that are used to sign well known
|
||||||
// ConfigMaps. They may also eventually be used for authentication.
|
// ConfigMaps. They may also eventually be used for authentication.
|
||||||
|
|
|
@ -22,6 +22,7 @@ go_test(
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/bootstrap/api:go_default_library",
|
"//pkg/bootstrap/api:go_default_library",
|
||||||
"//vendor:github.com/davecgh/go-spew/spew",
|
"//vendor:github.com/davecgh/go-spew/spew",
|
||||||
|
"//vendor:github.com/stretchr/testify/assert",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
"//vendor:k8s.io/client-go/kubernetes/fake",
|
"//vendor:k8s.io/client-go/kubernetes/fake",
|
||||||
|
|
|
@ -277,7 +277,9 @@ func (e *BootstrapSigner) getTokens() map[string]string {
|
||||||
|
|
||||||
// Check and warn for duplicate secrets. Behavior here will be undefined.
|
// Check and warn for duplicate secrets. Behavior here will be undefined.
|
||||||
if _, ok := ret[tokenID]; ok {
|
if _, ok := ret[tokenID]; ok {
|
||||||
glog.V(3).Infof("Duplicate bootstrap tokens found for id %s, ignoring on in %s/%s", tokenID, secret.Namespace, secret.Name)
|
// This should never happen as we ensure a consistent secret name.
|
||||||
|
// But leave this in here just in case.
|
||||||
|
glog.V(1).Infof("Duplicate bootstrap tokens found for id %s, ignoring on in %s/%s", tokenID, secret.Namespace, secret.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ func init() {
|
||||||
spew.Config.DisableMethods = true
|
spew.Config.DisableMethods = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testTokenID = "abc123"
|
||||||
|
|
||||||
func newBootstrapSigner() (*BootstrapSigner, *fake.Clientset) {
|
func newBootstrapSigner() (*BootstrapSigner, *fake.Clientset) {
|
||||||
options := DefaultBootstrapSignerOptions()
|
options := DefaultBootstrapSignerOptions()
|
||||||
cl := fake.NewSimpleClientset()
|
cl := fake.NewSimpleClientset()
|
||||||
|
@ -69,7 +71,7 @@ func TestSimpleSign(t *testing.T) {
|
||||||
cm := newConfigMap("", "")
|
cm := newConfigMap("", "")
|
||||||
signer.configMaps.Add(cm)
|
signer.configMaps.Add(cm)
|
||||||
|
|
||||||
secret := newTokenSecret("tokenID", "tokenSecret")
|
secret := newTokenSecret(testTokenID, "tokenSecret")
|
||||||
addSecretSigningUsage(secret, "true")
|
addSecretSigningUsage(secret, "true")
|
||||||
signer.secrets.Add(secret)
|
signer.secrets.Add(secret)
|
||||||
|
|
||||||
|
@ -78,7 +80,7 @@ func TestSimpleSign(t *testing.T) {
|
||||||
expected := []core.Action{
|
expected := []core.Action{
|
||||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"},
|
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"},
|
||||||
api.NamespacePublic,
|
api.NamespacePublic,
|
||||||
newConfigMap("tokenID", "eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE")),
|
newConfigMap(testTokenID, "eyJhbGciOiJIUzI1NiIsImtpZCI6ImFiYzEyMyJ9..QSxpUG7Q542CirTI2ECPSZjvBOJURUW5a7XqFpNI958")),
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyActions(t, expected, cl.Actions())
|
verifyActions(t, expected, cl.Actions())
|
||||||
|
@ -87,10 +89,10 @@ func TestSimpleSign(t *testing.T) {
|
||||||
func TestNoSignNeeded(t *testing.T) {
|
func TestNoSignNeeded(t *testing.T) {
|
||||||
signer, cl := newBootstrapSigner()
|
signer, cl := newBootstrapSigner()
|
||||||
|
|
||||||
cm := newConfigMap("tokenID", "eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE")
|
cm := newConfigMap(testTokenID, "eyJhbGciOiJIUzI1NiIsImtpZCI6ImFiYzEyMyJ9..QSxpUG7Q542CirTI2ECPSZjvBOJURUW5a7XqFpNI958")
|
||||||
signer.configMaps.Add(cm)
|
signer.configMaps.Add(cm)
|
||||||
|
|
||||||
secret := newTokenSecret("tokenID", "tokenSecret")
|
secret := newTokenSecret(testTokenID, "tokenSecret")
|
||||||
addSecretSigningUsage(secret, "true")
|
addSecretSigningUsage(secret, "true")
|
||||||
signer.secrets.Add(secret)
|
signer.secrets.Add(secret)
|
||||||
|
|
||||||
|
@ -102,10 +104,10 @@ func TestNoSignNeeded(t *testing.T) {
|
||||||
func TestUpdateSignature(t *testing.T) {
|
func TestUpdateSignature(t *testing.T) {
|
||||||
signer, cl := newBootstrapSigner()
|
signer, cl := newBootstrapSigner()
|
||||||
|
|
||||||
cm := newConfigMap("tokenID", "old signature")
|
cm := newConfigMap(testTokenID, "old signature")
|
||||||
signer.configMaps.Add(cm)
|
signer.configMaps.Add(cm)
|
||||||
|
|
||||||
secret := newTokenSecret("tokenID", "tokenSecret")
|
secret := newTokenSecret(testTokenID, "tokenSecret")
|
||||||
addSecretSigningUsage(secret, "true")
|
addSecretSigningUsage(secret, "true")
|
||||||
signer.secrets.Add(secret)
|
signer.secrets.Add(secret)
|
||||||
|
|
||||||
|
@ -114,7 +116,7 @@ func TestUpdateSignature(t *testing.T) {
|
||||||
expected := []core.Action{
|
expected := []core.Action{
|
||||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"},
|
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"},
|
||||||
api.NamespacePublic,
|
api.NamespacePublic,
|
||||||
newConfigMap("tokenID", "eyJhbGciOiJIUzI1NiIsImtpZCI6InRva2VuSUQifQ..QAvK9DAjF0hSyASEkH1MOTB5rJMmbWEY9j-z1NSYILE")),
|
newConfigMap(testTokenID, "eyJhbGciOiJIUzI1NiIsImtpZCI6ImFiYzEyMyJ9..QSxpUG7Q542CirTI2ECPSZjvBOJURUW5a7XqFpNI958")),
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyActions(t, expected, cl.Actions())
|
verifyActions(t, expected, cl.Actions())
|
||||||
|
@ -123,7 +125,7 @@ func TestUpdateSignature(t *testing.T) {
|
||||||
func TestRemoveSignature(t *testing.T) {
|
func TestRemoveSignature(t *testing.T) {
|
||||||
signer, cl := newBootstrapSigner()
|
signer, cl := newBootstrapSigner()
|
||||||
|
|
||||||
cm := newConfigMap("tokenID", "old signature")
|
cm := newConfigMap(testTokenID, "old signature")
|
||||||
signer.configMaps.Add(cm)
|
signer.configMaps.Add(cm)
|
||||||
|
|
||||||
signer.signConfigMap()
|
signer.signConfigMap()
|
||||||
|
|
|
@ -32,7 +32,7 @@ func newTokenSecret(tokenID, tokenSecret string) *v1.Secret {
|
||||||
return &v1.Secret{
|
return &v1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: metav1.NamespaceSystem,
|
Namespace: metav1.NamespaceSystem,
|
||||||
Name: "secretName",
|
Name: bootstrapapi.BootstrapTokenSecretPrefix + tokenID,
|
||||||
ResourceVersion: "1",
|
ResourceVersion: "1",
|
||||||
},
|
},
|
||||||
Type: bootstrapapi.SecretTypeBootstrapToken,
|
Type: bootstrapapi.SecretTypeBootstrapToken,
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
@ -25,6 +26,9 @@ import (
|
||||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var namePattern = `^` + regexp.QuoteMeta(bootstrapapi.BootstrapTokenSecretPrefix) + `([a-z0-9]{6})$`
|
||||||
|
var nameRegExp = regexp.MustCompile(namePattern)
|
||||||
|
|
||||||
// getSecretString gets a string value from a secret. If there is an error or
|
// getSecretString gets a string value from a secret. If there is an error or
|
||||||
// if the key doesn't exist, an empty string is returned.
|
// if the key doesn't exist, an empty string is returned.
|
||||||
func getSecretString(secret *v1.Secret, key string) string {
|
func getSecretString(secret *v1.Secret, key string) string {
|
||||||
|
@ -36,13 +40,33 @@ func getSecretString(secret *v1.Secret, key string) string {
|
||||||
return string(data)
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseSecretName parses the name of the secret to extract the secret ID.
|
||||||
|
func parseSecretName(name string) (secretID string, ok bool) {
|
||||||
|
r := nameRegExp.FindStringSubmatch(name)
|
||||||
|
if r == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return r[1], true
|
||||||
|
}
|
||||||
|
|
||||||
func validateSecretForSigning(secret *v1.Secret) (tokenID, tokenSecret string, ok bool) {
|
func validateSecretForSigning(secret *v1.Secret) (tokenID, tokenSecret string, ok bool) {
|
||||||
|
nameTokenID, ok := parseSecretName(secret.Name)
|
||||||
|
if !ok {
|
||||||
|
glog.V(3).Infof("Invalid secret name: %s. Must be of form %s<secret-id>.", secret.Name, bootstrapapi.BootstrapTokenSecretPrefix)
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
tokenID = getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
|
tokenID = getSecretString(secret, bootstrapapi.BootstrapTokenIDKey)
|
||||||
if len(tokenID) == 0 {
|
if len(tokenID) == 0 {
|
||||||
glog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenIDKey, secret.Namespace, secret.Name)
|
glog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenIDKey, secret.Namespace, secret.Name)
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nameTokenID != tokenID {
|
||||||
|
glog.V(3).Infof("Token ID (%s) doesn't match secret name: %s", tokenID, nameTokenID)
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
tokenSecret = getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
|
tokenSecret = getSecretString(secret, bootstrapapi.BootstrapTokenSecretKey)
|
||||||
if len(tokenSecret) == 0 {
|
if len(tokenSecret) == 0 {
|
||||||
glog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenSecretKey, secret.Namespace, secret.Name)
|
glog.V(3).Infof("No %s key in %s/%s Secret", bootstrapapi.BootstrapTokenSecretKey, secret.Namespace, secret.Name)
|
||||||
|
|
|
@ -20,13 +20,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
givenTokenID = "tokenID"
|
givenTokenID = "abc123"
|
||||||
|
givenTokenID2 = "def456"
|
||||||
givenTokenSecret = "tokenSecret"
|
givenTokenSecret = "tokenSecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,7 +84,7 @@ func TestValidateSecretForSigning(t *testing.T) {
|
||||||
secret := &v1.Secret{
|
secret := &v1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: metav1.NamespaceSystem,
|
Namespace: metav1.NamespaceSystem,
|
||||||
Name: "secretName",
|
Name: bootstrapapi.BootstrapTokenSecretPrefix + givenTokenID,
|
||||||
ResourceVersion: "1",
|
ResourceVersion: "1",
|
||||||
},
|
},
|
||||||
Type: bootstrapapi.SecretTypeBootstrapToken,
|
Type: bootstrapapi.SecretTypeBootstrapToken,
|
||||||
|
@ -113,7 +116,7 @@ func TestValidateSecret(t *testing.T) {
|
||||||
secret := &v1.Secret{
|
secret := &v1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: metav1.NamespaceSystem,
|
Namespace: metav1.NamespaceSystem,
|
||||||
Name: "secretName",
|
Name: bootstrapapi.BootstrapTokenSecretPrefix + givenTokenID,
|
||||||
ResourceVersion: "1",
|
ResourceVersion: "1",
|
||||||
},
|
},
|
||||||
Type: bootstrapapi.SecretTypeBootstrapToken,
|
Type: bootstrapapi.SecretTypeBootstrapToken,
|
||||||
|
@ -135,3 +138,69 @@ func TestValidateSecret(t *testing.T) {
|
||||||
t.Errorf("Unexpected Token Secret. Expected %q, got %q", givenTokenSecret, tokenSecret)
|
t.Errorf("Unexpected Token Secret. Expected %q, got %q", givenTokenSecret, tokenSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBadSecretName(t *testing.T) {
|
||||||
|
secret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
Name: givenTokenID,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Type: bootstrapapi.SecretTypeBootstrapToken,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
bootstrapapi.BootstrapTokenIDKey: []byte(givenTokenID),
|
||||||
|
bootstrapapi.BootstrapTokenSecretKey: []byte(givenTokenSecret),
|
||||||
|
bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ok := validateSecretForSigning(secret)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Token validation should fail with bad name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMismatchSecretName(t *testing.T) {
|
||||||
|
secret := &v1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: metav1.NamespaceSystem,
|
||||||
|
Name: bootstrapapi.BootstrapTokenSecretPrefix + givenTokenID2,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Type: bootstrapapi.SecretTypeBootstrapToken,
|
||||||
|
Data: map[string][]byte{
|
||||||
|
bootstrapapi.BootstrapTokenIDKey: []byte(givenTokenID),
|
||||||
|
bootstrapapi.BootstrapTokenSecretKey: []byte(givenTokenSecret),
|
||||||
|
bootstrapapi.BootstrapTokenUsageSigningKey: []byte("true"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ok := validateSecretForSigning(secret)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("Token validation should fail with mismatched name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSecretName(t *testing.T) {
|
||||||
|
tokenID, ok := parseSecretName("bootstrap-token-abc123")
|
||||||
|
assert.True(t, ok, "parseSecretName should accept valid name")
|
||||||
|
assert.Equal(t, "abc123", tokenID, "parseSecretName should return token ID")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject blank name")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("abc123")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject with no prefix")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("bootstrap-token-")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject no token ID")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("bootstrap-token-abc")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject short token ID")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("bootstrap-token-abc123ghi")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject long token ID")
|
||||||
|
|
||||||
|
_, ok = parseSecretName("bootstrap-token-ABC123")
|
||||||
|
assert.False(t, ok, "parseSecretName should reject invalid token ID")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue