Certificate-less bootstrap tokens (#93)
This commit is contained in:
parent
dcc954631b
commit
c4c1851aff
|
|
@ -30,10 +30,6 @@ type BootstrapToken struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(rawCertificate []byte, serviceAccountName string, serviceAccountToken string) (*BootstrapToken, error) {
|
func New(rawCertificate []byte, serviceAccountName string, serviceAccountToken string) (*BootstrapToken, error) {
|
||||||
if len(rawCertificate) == 0 {
|
|
||||||
return nil, fmt.Errorf("%w: empty certificate", ErrFailedToCreateBootstrapToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if serviceAccountName == "" {
|
if serviceAccountName == "" {
|
||||||
return nil, fmt.Errorf("%w: empty service account name", ErrFailedToCreateBootstrapToken)
|
return nil, fmt.Errorf("%w: empty service account name", ErrFailedToCreateBootstrapToken)
|
||||||
}
|
}
|
||||||
|
|
@ -42,17 +38,22 @@ func New(rawCertificate []byte, serviceAccountName string, serviceAccountToken s
|
||||||
return nil, fmt.Errorf("%w: empty service account token", ErrFailedToCreateBootstrapToken)
|
return nil, fmt.Errorf("%w: empty service account token", ErrFailedToCreateBootstrapToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse certificate
|
// Optionally parse a certificate
|
||||||
block, _ := pem.Decode(rawCertificate)
|
var certificate *x509.Certificate
|
||||||
if block == nil {
|
var err error
|
||||||
return nil, fmt.Errorf("%w: failed to parse certificate: expected a PEM format",
|
|
||||||
ErrFailedToCreateBootstrapToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate, err := x509.ParseCertificate(block.Bytes)
|
if len(rawCertificate) != 0 {
|
||||||
if err != nil {
|
block, _ := pem.Decode(rawCertificate)
|
||||||
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
|
if block == nil {
|
||||||
ErrFailedToCreateBootstrapToken, err)
|
return nil, fmt.Errorf("%w: failed to parse certificate: expected a PEM format",
|
||||||
|
ErrFailedToCreateBootstrapToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
|
||||||
|
ErrFailedToCreateBootstrapToken, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BootstrapToken{
|
return &BootstrapToken{
|
||||||
|
|
@ -78,10 +79,6 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
|
||||||
return nil, fmt.Errorf("%w: missing service account credentials", ErrInvalidBootstrapTokenFormat)
|
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 {
|
if len(splits) > 4 {
|
||||||
return nil, fmt.Errorf("%w: extraneous data", ErrInvalidBootstrapTokenFormat)
|
return nil, fmt.Errorf("%w: extraneous data", ErrInvalidBootstrapTokenFormat)
|
||||||
}
|
}
|
||||||
|
|
@ -96,19 +93,25 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
|
||||||
return nil, fmt.Errorf("%w: failed to decode service account token: %v",
|
return nil, fmt.Errorf("%w: failed to decode service account token: %v",
|
||||||
ErrInvalidBootstrapTokenFormat, err)
|
ErrInvalidBootstrapTokenFormat, err)
|
||||||
}
|
}
|
||||||
rawCertificate, err := encoding.DecodeString(splits[3])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: failed to decode certificate: %v",
|
|
||||||
ErrInvalidBootstrapTokenFormat, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse certificate
|
// Optionally parse the certificate
|
||||||
block, _ := pem.Decode(rawCertificate)
|
var certificate *x509.Certificate
|
||||||
|
var rawCertificate []byte
|
||||||
|
|
||||||
certificate, err := x509.ParseCertificate(block.Bytes)
|
if len(splits) == 4 {
|
||||||
if err != nil {
|
rawCertificate, err = encoding.DecodeString(splits[3])
|
||||||
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
|
if err != nil {
|
||||||
ErrFailedToCreateBootstrapToken, err)
|
return nil, fmt.Errorf("%w: failed to decode certificate: %v",
|
||||||
|
ErrInvalidBootstrapTokenFormat, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(rawCertificate)
|
||||||
|
|
||||||
|
certificate, err = x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: failed to parse certificate: %v",
|
||||||
|
ErrFailedToCreateBootstrapToken, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BootstrapToken{
|
return &BootstrapToken{
|
||||||
|
|
@ -121,12 +124,19 @@ func NewFromString(rawBootstrapToken string) (*BootstrapToken, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bt *BootstrapToken) String() string {
|
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,
|
versionPrefix,
|
||||||
version,
|
version,
|
||||||
encoding.EncodeToString([]byte(bt.serviceAccountName)),
|
encoding.EncodeToString([]byte(bt.serviceAccountName)),
|
||||||
encoding.EncodeToString([]byte(bt.serviceAccountToken)),
|
encoding.EncodeToString([]byte(bt.serviceAccountToken)),
|
||||||
encoding.EncodeToString(bt.rawCertificate),
|
certificatePart,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBootstrapTokenTwoWay(t *testing.T) {
|
func TestBootstrapTokenTwoWay(t *testing.T) {
|
||||||
serviceAccountName := "admin"
|
|
||||||
serviceAccountToken := uuid.New().String()
|
|
||||||
|
|
||||||
tlsCert, err := controllercmd.GenerateSelfSignedControllerCertificate()
|
tlsCert, err := controllercmd.GenerateSelfSignedControllerCertificate()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
@ -22,7 +19,19 @@ func TestBootstrapTokenTwoWay(t *testing.T) {
|
||||||
}
|
}
|
||||||
certificatePEM := pem.EncodeToMemory(block)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bootstrapTokenNew, err := bootstraptoken.NewFromString(bootstrapTokenOld.String())
|
bootstrapTokenNew, err := bootstraptoken.NewFromString(bootstrapTokenOld.String())
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ var ErrBootstrapTokenNotProvided = errors.New("no bootstrap token provided")
|
||||||
var bootstrapTokenRaw string
|
var bootstrapTokenRaw string
|
||||||
var logFilePath string
|
var logFilePath string
|
||||||
var stringToStringResources map[string]string
|
var stringToStringResources map[string]string
|
||||||
|
var noPKI bool
|
||||||
|
|
||||||
func newRunCommand() *cobra.Command {
|
func newRunCommand() *cobra.Command {
|
||||||
cmd := &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.")
|
"optional path to a file where logs (up to 100 Mb) will be written.")
|
||||||
cmd.PersistentFlags().StringToStringVar(&stringToStringResources, "resources", map[string]string{},
|
cmd.PersistentFlags().StringToStringVar(&stringToStringResources, "resources", map[string]string{},
|
||||||
"resources that this worker provides")
|
"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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWorker(cmd *cobra.Command, args []string) (err error) {
|
func runWorker(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
// Parse controller URL
|
||||||
controllerURL, err := netconstants.NormalizeAddress(args[0])
|
controllerURL, err := netconstants.NormalizeAddress(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse bootstrap token
|
||||||
if bootstrapTokenRaw == "" {
|
if bootstrapTokenRaw == "" {
|
||||||
return ErrBootstrapTokenNotProvided
|
return ErrBootstrapTokenNotProvided
|
||||||
}
|
}
|
||||||
|
|
@ -59,11 +67,19 @@ func runWorker(cmd *cobra.Command, args []string) (err error) {
|
||||||
return fmt.Errorf("%w: %v", ErrRunFailed, err)
|
return fmt.Errorf("%w: %v", ErrRunFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
controllerClient, err := client.New(
|
clientOpts := []client.Option{
|
||||||
client.WithAddress(controllerURL.String()),
|
client.WithAddress(controllerURL.String()),
|
||||||
client.WithTrustedCertificate(bootstrapToken.Certificate()),
|
|
||||||
client.WithCredentials(bootstrapToken.ServiceAccountName(), bootstrapToken.ServiceAccountToken()),
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue