Certificate-less bootstrap tokens (#93)

This commit is contained in:
Nikolay Edigaryev 2023-06-23 00:53:03 +04:00 committed by GitHub
parent dcc954631b
commit c4c1851aff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 38 deletions

View File

@ -30,10 +30,6 @@ type BootstrapToken struct {
}
func New(rawCertificate []byte, serviceAccountName string, serviceAccountToken string) (*BootstrapToken, error) {
if len(rawCertificate) == 0 {
return nil, fmt.Errorf("%w: empty certificate", ErrFailedToCreateBootstrapToken)
}
if serviceAccountName == "" {
return nil, fmt.Errorf("%w: empty service account name", ErrFailedToCreateBootstrapToken)
}
@ -42,18 +38,23 @@ func New(rawCertificate []byte, serviceAccountName string, serviceAccountToken s
return nil, fmt.Errorf("%w: empty service account token", ErrFailedToCreateBootstrapToken)
}
// Parse certificate
// Optionally parse a certificate
var certificate *x509.Certificate
var err error
if len(rawCertificate) != 0 {
block, _ := pem.Decode(rawCertificate)
if block == nil {
return nil, fmt.Errorf("%w: failed to parse certificate: expected a PEM format",
ErrFailedToCreateBootstrapToken)
}
certificate, err := x509.ParseCertificate(block.Bytes)
certificate, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
ErrFailedToCreateBootstrapToken, err)
}
}
return &BootstrapToken{
version: version,
@ -78,10 +79,6 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
return nil, fmt.Errorf("%w: missing service account credentials", ErrInvalidBootstrapTokenFormat)
}
if len(splits) < 4 {
return nil, fmt.Errorf("%w: missing certificate", ErrInvalidBootstrapTokenFormat)
}
if len(splits) > 4 {
return nil, fmt.Errorf("%w: extraneous data", ErrInvalidBootstrapTokenFormat)
}
@ -96,20 +93,26 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
return nil, fmt.Errorf("%w: failed to decode service account token: %v",
ErrInvalidBootstrapTokenFormat, err)
}
rawCertificate, err := encoding.DecodeString(splits[3])
// Optionally parse the certificate
var certificate *x509.Certificate
var rawCertificate []byte
if len(splits) == 4 {
rawCertificate, err = encoding.DecodeString(splits[3])
if err != nil {
return nil, fmt.Errorf("%w: failed to decode certificate: %v",
ErrInvalidBootstrapTokenFormat, err)
}
// Parse certificate
block, _ := pem.Decode(rawCertificate)
certificate, err := x509.ParseCertificate(block.Bytes)
certificate, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
ErrFailedToCreateBootstrapToken, err)
}
}
return &BootstrapToken{
version: version,
@ -121,12 +124,19 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
}
func (bt *BootstrapToken) String() string {
return fmt.Sprintf("%s%d.%s.%s.%s",
var certificatePart string
// Certificate is optional
if len(bt.rawCertificate) != 0 {
certificatePart = fmt.Sprintf(".%s", encoding.EncodeToString(bt.rawCertificate))
}
return fmt.Sprintf("%s%d.%s.%s%s",
versionPrefix,
version,
encoding.EncodeToString([]byte(bt.serviceAccountName)),
encoding.EncodeToString([]byte(bt.serviceAccountToken)),
encoding.EncodeToString(bt.rawCertificate),
certificatePart,
)
}

View File

@ -10,9 +10,6 @@ import (
)
func TestBootstrapTokenTwoWay(t *testing.T) {
serviceAccountName := "admin"
serviceAccountToken := uuid.New().String()
tlsCert, err := controllercmd.GenerateSelfSignedControllerCertificate()
require.NoError(t, err)
@ -22,7 +19,19 @@ func TestBootstrapTokenTwoWay(t *testing.T) {
}
certificatePEM := pem.EncodeToMemory(block)
bootstrapTokenOld, err := bootstraptoken.New(certificatePEM, serviceAccountName, serviceAccountToken)
bootstrapTokenOld, err := bootstraptoken.New(certificatePEM, uuid.NewString(), uuid.NewString())
require.NoError(t, err)
bootstrapTokenNew, err := bootstraptoken.NewFromString(bootstrapTokenOld.String())
require.NoError(t, err)
require.Equal(t, bootstrapTokenOld.ServiceAccountName(), bootstrapTokenNew.ServiceAccountName())
require.Equal(t, bootstrapTokenOld.ServiceAccountToken(), bootstrapTokenNew.ServiceAccountToken())
require.Equal(t, bootstrapTokenOld.Certificate(), bootstrapTokenNew.Certificate())
}
func TestBootstrapTokenTwoWayEmptyCertificate(t *testing.T) {
bootstrapTokenOld, err := bootstraptoken.New([]byte{}, uuid.NewString(), uuid.NewString())
require.NoError(t, err)
bootstrapTokenNew, err := bootstraptoken.NewFromString(bootstrapTokenOld.String())

View File

@ -21,6 +21,7 @@ var ErrBootstrapTokenNotProvided = errors.New("no bootstrap token provided")
var bootstrapTokenRaw string
var logFilePath string
var stringToStringResources map[string]string
var noPKI bool
func newRunCommand() *cobra.Command {
cmd := &cobra.Command{
@ -36,15 +37,22 @@ func newRunCommand() *cobra.Command {
"optional path to a file where logs (up to 100 Mb) will be written.")
cmd.PersistentFlags().StringToStringVar(&stringToStringResources, "resources", map[string]string{},
"resources that this worker provides")
cmd.PersistentFlags().BoolVar(&noPKI, "no-pki", false,
"do not use the host's root CA set and instead validate the Controller's presented "+
"certificate using a bootstrap token (or manually via fingerprint, "+
"if no bootstrap token is provided)")
return cmd
}
func runWorker(cmd *cobra.Command, args []string) (err error) {
// Parse controller URL
controllerURL, err := netconstants.NormalizeAddress(args[0])
if err != nil {
return err
}
// Parse bootstrap token
if bootstrapTokenRaw == "" {
return ErrBootstrapTokenNotProvided
}
@ -59,11 +67,19 @@ func runWorker(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("%w: %v", ErrRunFailed, err)
}
controllerClient, err := client.New(
clientOpts := []client.Option{
client.WithAddress(controllerURL.String()),
client.WithTrustedCertificate(bootstrapToken.Certificate()),
client.WithCredentials(bootstrapToken.ServiceAccountName(), bootstrapToken.ServiceAccountToken()),
)
}
if trustedCertificate := bootstrapToken.Certificate(); trustedCertificate != nil {
clientOpts = append(clientOpts, client.WithTrustedCertificate(trustedCertificate))
} else if noPKI {
return fmt.Errorf("%w: --no-pki was specified, but not trusted certificate was provided "+
"in the bootstrap token", ErrRunFailed)
}
controllerClient, err := client.New(clientOpts...)
if err != nil {
return err
}