diff --git a/src/assisted_installer_controller/reboots_notifier.go b/src/assisted_installer_controller/reboots_notifier.go index 9e2eeeaa0d..ceb3e8fef9 100644 --- a/src/assisted_installer_controller/reboots_notifier.go +++ b/src/assisted_installer_controller/reboots_notifier.go @@ -8,8 +8,8 @@ import ( "time" "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" "github.com/openshift/assisted-installer/src/common" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/inventory_client" "github.com/openshift/assisted-installer/src/ops" "github.com/openshift/assisted-installer/src/utils" @@ -97,8 +97,8 @@ func (r *rebootsNotifier) run(ctx context.Context, nodeName string, hostId, infr ClusterID: clusterId, Name: eventName, Category: models.EventCategoryUser, - Severity: swag.String(models.EventSeverityInfo), - Message: swag.String(fmt.Sprintf(eventMessageTemplate, nodeName, numberOfReboots)), + Severity: convert.String(models.EventSeverityInfo), + Message: convert.String(fmt.Sprintf(eventMessageTemplate, nodeName, numberOfReboots)), } if err = r.ic.TriggerEvent(ctx, ev); err != nil { diff --git a/src/assisted_installer_controller/reboots_notifier_test.go b/src/assisted_installer_controller/reboots_notifier_test.go index 6344d29518..0c1a416229 100644 --- a/src/assisted_installer_controller/reboots_notifier_test.go +++ b/src/assisted_installer_controller/reboots_notifier_test.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" "github.com/google/uuid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/inventory_client" "github.com/openshift/assisted-installer/src/ops" "github.com/openshift/assisted-service/models" @@ -71,9 +71,9 @@ var _ = Describe("Reboots notifier", func() { ClusterID: &clusterId, HostID: &hostId, InfraEnvID: &infraenvId, - Message: swag.String(fmt.Sprintf(eventMessageTemplate, nodeName, 1)), + Message: convert.String(fmt.Sprintf(eventMessageTemplate, nodeName, 1)), Name: eventName, - Severity: swag.String(models.EventSeverityInfo), + Severity: convert.String(models.EventSeverityInfo), }).Return(nil) notifier.Start(context.TODO(), nodeName, &hostId, &infraenvId, &clusterId) notifier.Finalize() diff --git a/src/common/common.go b/src/common/common.go index c202ee1e22..ea522c355e 100644 --- a/src/common/common.go +++ b/src/common/common.go @@ -3,6 +3,10 @@ package common import ( "bytes" "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509" + "encoding/pem" "fmt" "io" "os" @@ -16,6 +20,8 @@ import ( "github.com/openshift/assisted-installer/src/utils" "github.com/openshift/assisted-service/models" + cryptorand "crypto/rand" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/thoas/go-funk" @@ -35,6 +41,7 @@ const ( installConfigMapAttribute = "invoker" InvokerAssisted = "assisted-service" InvokerAgent = "agent-installer" + ECPrivateKeyPEMLabel = "EC PRIVATE KEY" ) func GetHostsInStatus(hosts map[string]inventory_client.HostData, status []string, isMatch bool) map[string]inventory_client.HostData { @@ -306,3 +313,22 @@ func DownloadKubeconfigNoingress(ctx context.Context, dir string, ic inventory_c return kubeconfigPath, nil } + +func MakeEllipticPrivatePublicKeyPems() ([]byte, []byte, error) { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), cryptorand.Reader) + if err != nil { + return nil, nil, err + } + derBytes, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) + if err != nil { + return nil, nil, err + } + return pem.EncodeToMemory(&pem.Block{ + Type: ECPrivateKeyPEMLabel, + Bytes: derBytes, + }), publicKeyBytes, nil +} diff --git a/src/convert/convert_types.go b/src/convert/convert_types.go new file mode 100644 index 0000000000..e744a779d3 --- /dev/null +++ b/src/convert/convert_types.go @@ -0,0 +1,730 @@ +package convert + +import "time" + +// This file was taken from the aws go sdk + +// String returns a pointer to of the string value passed in. +func String(v string) *string { + return &v +} + +// StringValue returns the value of the string pointer passed in or +// "" if the pointer is nil. +func StringValue(v *string) string { + if v != nil { + return *v + } + return "" +} + +// StringSlice converts a slice of string values into a slice of +// string pointers +func StringSlice(src []string) []*string { + dst := make([]*string, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// StringValueSlice converts a slice of string pointers into a slice of +// string values +func StringValueSlice(src []*string) []string { + dst := make([]string, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// StringMap converts a string map of string values into a string +// map of string pointers +func StringMap(src map[string]string) map[string]*string { + dst := make(map[string]*string) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// StringValueMap converts a string map of string pointers into a string +// map of string values +func StringValueMap(src map[string]*string) map[string]string { + dst := make(map[string]string) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Bool returns a pointer to of the bool value passed in. +func Bool(v bool) *bool { + return &v +} + +// BoolValue returns the value of the bool pointer passed in or +// false if the pointer is nil. +func BoolValue(v *bool) bool { + if v != nil { + return *v + } + return false +} + +// BoolSlice converts a slice of bool values into a slice of +// bool pointers +func BoolSlice(src []bool) []*bool { + dst := make([]*bool, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// BoolValueSlice converts a slice of bool pointers into a slice of +// bool values +func BoolValueSlice(src []*bool) []bool { + dst := make([]bool, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// BoolMap converts a string map of bool values into a string +// map of bool pointers +func BoolMap(src map[string]bool) map[string]*bool { + dst := make(map[string]*bool) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// BoolValueMap converts a string map of bool pointers into a string +// map of bool values +func BoolValueMap(src map[string]*bool) map[string]bool { + dst := make(map[string]bool) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int returns a pointer to of the int value passed in. +func Int(v int) *int { + return &v +} + +// IntValue returns the value of the int pointer passed in or +// 0 if the pointer is nil. +func IntValue(v *int) int { + if v != nil { + return *v + } + return 0 +} + +// IntSlice converts a slice of int values into a slice of +// int pointers +func IntSlice(src []int) []*int { + dst := make([]*int, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// IntValueSlice converts a slice of int pointers into a slice of +// int values +func IntValueSlice(src []*int) []int { + dst := make([]int, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// IntMap converts a string map of int values into a string +// map of int pointers +func IntMap(src map[string]int) map[string]*int { + dst := make(map[string]*int) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// IntValueMap converts a string map of int pointers into a string +// map of int values +func IntValueMap(src map[string]*int) map[string]int { + dst := make(map[string]int) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int32 returns a pointer to of the int32 value passed in. +func Int32(v int32) *int32 { + return &v +} + +// Int32Value returns the value of the int32 pointer passed in or +// 0 if the pointer is nil. +func Int32Value(v *int32) int32 { + if v != nil { + return *v + } + return 0 +} + +// Int32Slice converts a slice of int32 values into a slice of +// int32 pointers +func Int32Slice(src []int32) []*int32 { + dst := make([]*int32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int32ValueSlice converts a slice of int32 pointers into a slice of +// int32 values +func Int32ValueSlice(src []*int32) []int32 { + dst := make([]int32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int32Map converts a string map of int32 values into a string +// map of int32 pointers +func Int32Map(src map[string]int32) map[string]*int32 { + dst := make(map[string]*int32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int32ValueMap converts a string map of int32 pointers into a string +// map of int32 values +func Int32ValueMap(src map[string]*int32) map[string]int32 { + dst := make(map[string]int32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int64 returns a pointer to of the int64 value passed in. +func Int64(v int64) *int64 { + return &v +} + +// Int64Value returns the value of the int64 pointer passed in or +// 0 if the pointer is nil. +func Int64Value(v *int64) int64 { + if v != nil { + return *v + } + return 0 +} + +// Int64Slice converts a slice of int64 values into a slice of +// int64 pointers +func Int64Slice(src []int64) []*int64 { + dst := make([]*int64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int64ValueSlice converts a slice of int64 pointers into a slice of +// int64 values +func Int64ValueSlice(src []*int64) []int64 { + dst := make([]int64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int64Map converts a string map of int64 values into a string +// map of int64 pointers +func Int64Map(src map[string]int64) map[string]*int64 { + dst := make(map[string]*int64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int64ValueMap converts a string map of int64 pointers into a string +// map of int64 values +func Int64ValueMap(src map[string]*int64) map[string]int64 { + dst := make(map[string]int64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint16 returns a pointer to of the uint16 value passed in. +func Uint16(v uint16) *uint16 { + return &v +} + +// Uint16Value returns the value of the uint16 pointer passed in or +// 0 if the pointer is nil. +func Uint16Value(v *uint16) uint16 { + if v != nil { + return *v + } + + return 0 +} + +// Uint16Slice converts a slice of uint16 values into a slice of +// uint16 pointers +func Uint16Slice(src []uint16) []*uint16 { + dst := make([]*uint16, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Uint16ValueSlice converts a slice of uint16 pointers into a slice of +// uint16 values +func Uint16ValueSlice(src []*uint16) []uint16 { + dst := make([]uint16, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Uint16Map converts a string map of uint16 values into a string +// map of uint16 pointers +func Uint16Map(src map[string]uint16) map[string]*uint16 { + dst := make(map[string]*uint16) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Uint16ValueMap converts a string map of uint16 pointers into a string +// map of uint16 values +func Uint16ValueMap(src map[string]*uint16) map[string]uint16 { + dst := make(map[string]uint16) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + +// Uint returns a pointer to of the uint value passed in. +func Uint(v uint) *uint { + return &v +} + +// UintValue returns the value of the uint pointer passed in or +// 0 if the pointer is nil. +func UintValue(v *uint) uint { + if v != nil { + return *v + } + return 0 +} + +// UintSlice converts a slice of uint values into a slice of +// uint pointers +func UintSlice(src []uint) []*uint { + dst := make([]*uint, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// UintValueSlice converts a slice of uint pointers into a slice of +// uint values +func UintValueSlice(src []*uint) []uint { + dst := make([]uint, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// UintMap converts a string map of uint values into a string +// map of uint pointers +func UintMap(src map[string]uint) map[string]*uint { + dst := make(map[string]*uint) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// UintValueMap converts a string map of uint pointers into a string +// map of uint values +func UintValueMap(src map[string]*uint) map[string]uint { + dst := make(map[string]uint) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint32 returns a pointer to of the uint32 value passed in. +func Uint32(v uint32) *uint32 { + return &v +} + +// Uint32Value returns the value of the uint32 pointer passed in or +// 0 if the pointer is nil. +func Uint32Value(v *uint32) uint32 { + if v != nil { + return *v + } + return 0 +} + +// Uint32Slice converts a slice of uint32 values into a slice of +// uint32 pointers +func Uint32Slice(src []uint32) []*uint32 { + dst := make([]*uint32, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint32ValueSlice converts a slice of uint32 pointers into a slice of +// uint32 values +func Uint32ValueSlice(src []*uint32) []uint32 { + dst := make([]uint32, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint32Map converts a string map of uint32 values into a string +// map of uint32 pointers +func Uint32Map(src map[string]uint32) map[string]*uint32 { + dst := make(map[string]*uint32) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint32ValueMap converts a string map of uint32 pointers into a string +// map of uint32 values +func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { + dst := make(map[string]uint32) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Uint64 returns a pointer to of the uint64 value passed in. +func Uint64(v uint64) *uint64 { + return &v +} + +// Uint64Value returns the value of the uint64 pointer passed in or +// 0 if the pointer is nil. +func Uint64Value(v *uint64) uint64 { + if v != nil { + return *v + } + return 0 +} + +// Uint64Slice converts a slice of uint64 values into a slice of +// uint64 pointers +func Uint64Slice(src []uint64) []*uint64 { + dst := make([]*uint64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Uint64ValueSlice converts a slice of uint64 pointers into a slice of +// uint64 values +func Uint64ValueSlice(src []*uint64) []uint64 { + dst := make([]uint64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Uint64Map converts a string map of uint64 values into a string +// map of uint64 pointers +func Uint64Map(src map[string]uint64) map[string]*uint64 { + dst := make(map[string]*uint64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Uint64ValueMap converts a string map of uint64 pointers into a string +// map of uint64 values +func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { + dst := make(map[string]uint64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Float32 returns a pointer to of the float32 value passed in. +func Float32(v float32) *float32 { + return &v +} + +// Float32Value returns the value of the float32 pointer passed in or +// 0 if the pointer is nil. +func Float32Value(v *float32) float32 { + if v != nil { + return *v + } + + return 0 +} + +// Float32Slice converts a slice of float32 values into a slice of +// float32 pointers +func Float32Slice(src []float32) []*float32 { + dst := make([]*float32, len(src)) + + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + + return dst +} + +// Float32ValueSlice converts a slice of float32 pointers into a slice of +// float32 values +func Float32ValueSlice(src []*float32) []float32 { + dst := make([]float32, len(src)) + + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + + return dst +} + +// Float32Map converts a string map of float32 values into a string +// map of float32 pointers +func Float32Map(src map[string]float32) map[string]*float32 { + dst := make(map[string]*float32) + + for k, val := range src { + v := val + dst[k] = &v + } + + return dst +} + +// Float32ValueMap converts a string map of float32 pointers into a string +// map of float32 values +func Float32ValueMap(src map[string]*float32) map[string]float32 { + dst := make(map[string]float32) + + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + + return dst +} + +// Float64 returns a pointer to of the float64 value passed in. +func Float64(v float64) *float64 { + return &v +} + +// Float64Value returns the value of the float64 pointer passed in or +// 0 if the pointer is nil. +func Float64Value(v *float64) float64 { + if v != nil { + return *v + } + return 0 +} + +// Float64Slice converts a slice of float64 values into a slice of +// float64 pointers +func Float64Slice(src []float64) []*float64 { + dst := make([]*float64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Float64ValueSlice converts a slice of float64 pointers into a slice of +// float64 values +func Float64ValueSlice(src []*float64) []float64 { + dst := make([]float64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Float64Map converts a string map of float64 values into a string +// map of float64 pointers +func Float64Map(src map[string]float64) map[string]*float64 { + dst := make(map[string]*float64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Float64ValueMap converts a string map of float64 pointers into a string +// map of float64 values +func Float64ValueMap(src map[string]*float64) map[string]float64 { + dst := make(map[string]float64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Time returns a pointer to of the time.Time value passed in. +func Time(v time.Time) *time.Time { + return &v +} + +// TimeValue returns the value of the time.Time pointer passed in or +// time.Time{} if the pointer is nil. +func TimeValue(v *time.Time) time.Time { + if v != nil { + return *v + } + return time.Time{} +} + +// TimeSlice converts a slice of time.Time values into a slice of +// time.Time pointers +func TimeSlice(src []time.Time) []*time.Time { + dst := make([]*time.Time, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// TimeValueSlice converts a slice of time.Time pointers into a slice of +// time.Time values +func TimeValueSlice(src []*time.Time) []time.Time { + dst := make([]time.Time, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// TimeMap converts a string map of time.Time values into a string +// map of time.Time pointers +func TimeMap(src map[string]time.Time) map[string]*time.Time { + dst := make(map[string]*time.Time) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// TimeValueMap converts a string map of time.Time pointers into a string +// map of time.Time values +func TimeValueMap(src map[string]*time.Time) map[string]time.Time { + dst := make(map[string]time.Time) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} diff --git a/src/ignition/ignition.go b/src/ignition/ignition.go index 79f14a9321..10d2a6faef 100644 --- a/src/ignition/ignition.go +++ b/src/ignition/ignition.go @@ -2,13 +2,17 @@ package ignition import ( "encoding/json" + "fmt" "os" + "encoding/base64" + ignitionConfigPrevVersion "github.com/coreos/ignition/v2/config/v3_1" ignitionConfig "github.com/coreos/ignition/v2/config/v3_2" "github.com/coreos/ignition/v2/config/v3_2/translate" "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/coreos/ignition/v2/config/validate" + "github.com/openshift/assisted-installer/src/convert" "github.com/pkg/errors" ) @@ -21,6 +25,7 @@ type Ignition interface { ParseIgnitionFile(path string) (*types.Config, error) WriteIgnitionFile(path string, config *types.Config) error MergeIgnitionConfig(base *types.Config, overrides *types.Config) (*types.Config, error) + InjectKubeletTempPrivateKey(pathToSourceIgnition string, privateKeyBytes []byte, certPathToInject string) error } type ignition struct{} @@ -78,3 +83,32 @@ func (i *ignition) MergeIgnitionConfig(base *types.Config, overrides *types.Conf } return &config, nil } + +func (i *ignition) InjectKubeletTempPrivateKey(pathToSourceIgnition string, privateKeyBytes []byte, certPathToInject string) error { + sourceIgnition, err := i.ParseIgnitionFile(pathToSourceIgnition) + if err != nil { + return errors.Wrapf(err, "unable to parse ignition file %s", pathToSourceIgnition) + } + privateKeyFile := types.File{ + Node: types.Node{ + Overwrite: convert.Bool(true), + Path: certPathToInject, + User: types.NodeUser{ + Name: convert.String("root"), + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: convert.String( + fmt.Sprintf( + "data:text/plain;charset=utf-8;base64,%s", + base64.StdEncoding.EncodeToString([]byte(privateKeyBytes)), + ), + ), + }, + Mode: convert.Int(0600), + }, + } + sourceIgnition.Storage.Files = append(sourceIgnition.Storage.Files, privateKeyFile) + return nil +} \ No newline at end of file diff --git a/src/ignition/mock_ignition.go b/src/ignition/mock_ignition.go index 67aa6b7fd6..849af091a2 100644 --- a/src/ignition/mock_ignition.go +++ b/src/ignition/mock_ignition.go @@ -38,6 +38,20 @@ func (m *MockIgnition) EXPECT() *MockIgnitionMockRecorder { return m.recorder } +// InjectKubeletTempPrivateKey mocks base method. +func (m *MockIgnition) InjectKubeletTempPrivateKey(pathToSourceIgnition string, privateKeyBytes []byte, certPathToInject string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InjectKubeletTempPrivateKey", pathToSourceIgnition, privateKeyBytes, certPathToInject) + ret0, _ := ret[0].(error) + return ret0 +} + +// InjectKubeletTempPrivateKey indicates an expected call of InjectKubeletTempPrivateKey. +func (mr *MockIgnitionMockRecorder) InjectKubeletTempPrivateKey(pathToSourceIgnition, privateKeyBytes, certPathToInject interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectKubeletTempPrivateKey", reflect.TypeOf((*MockIgnition)(nil).InjectKubeletTempPrivateKey), pathToSourceIgnition, privateKeyBytes, certPathToInject) +} + // MergeIgnitionConfig mocks base method. func (m *MockIgnition) MergeIgnitionConfig(base, overrides *types.Config) (*types.Config, error) { m.ctrl.T.Helper() diff --git a/src/installer/installer.go b/src/installer/installer.go index 1f23d7e836..e0ef57dc5e 100644 --- a/src/installer/installer.go +++ b/src/installer/installer.go @@ -14,8 +14,8 @@ import ( "golang.org/x/sync/errgroup" v1 "k8s.io/api/core/v1" - "github.com/go-openapi/swag" "github.com/google/uuid" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/main/drymock" "github.com/openshift/assisted-service/pkg/secretdump" "github.com/openshift/assisted-service/pkg/validations" @@ -51,6 +51,7 @@ const ( singleNodeMasterIgnitionPath = "/opt/openshift/master.ign" waitingForMastersStatusInfo = "Waiting for masters to join bootstrap control plane" waitingForBootstrapToPrepare = "Waiting for bootstrap node preparation" + ECPrivateKeyBlockType = "EC PRIVATE KEY" ) var generalWaitTimeout = 30 * time.Second @@ -96,6 +97,20 @@ func (i *installer) FormatDisks() { } } +func (i *installer) StorePublicKeyInControlPlane(publicKey []byte, client k8s_client.K8SClient) error { + namespace := "assisted-node-public-keys" + err := client.CreateNamespace(namespace) + if err != nil { + i.log.WithError(err).Errorf("unable to create %s namespace on control plane", namespace) + } + err = client.CreateConfigMap(fmt.Sprintf("node-key-%s", i.HostID), namespace, map[string]string{"content": string(publicKey[:])}) + if err != nil { + i.log.WithError(err).Errorf("unable to create config map for host ID %s for on control plane", i.HostID) + } + return nil +} + + func (i *installer) InstallNode() error { i.log.Infof("Installing node with role: %s", i.Config.Role) @@ -141,6 +156,14 @@ func (i *installer) InstallNode() error { } + // privateKeyBytes, publicKeyBytes, err := ignition.MakePrivatePublicKeyPairForKubelet() + privateKeyBytes, publicKeyBytes, err := common.MakeEllipticPrivatePublicKeyPems() + if err != nil { + return errors.Wrap(err, "could not generate private/public key pair for temporary kubelet certificate") + } + ign := ignition.NewIgnition() + ign.InjectKubeletTempPrivateKey(ignitionPath, privateKeyBytes, "/var/lib/kubelet/pki/kubelet-client.key.tmp") + if err = i.writeImageToDisk(ignitionPath); err != nil { return err } @@ -150,6 +173,15 @@ func (i *installer) InstallNode() error { if err = i.workerWaitFor2ReadyMasters(ctx); err != nil { return err } + + kc, err := i.kcBuilder(KubeconfigPath, i.log) + if err != nil { + i.log.Error(err) + return err + } + if err := i.StorePublicKeyInControlPlane(publicKeyBytes, kc); err != nil { + return errors.Wrap(err, "unable to store public key for node in control plane") + } } if i.EnableSkipMcoReboot { @@ -171,6 +203,14 @@ func (i *installer) InstallNode() error { if err = i.waitForControlPlane(ctx); err != nil { return err } + kc, err := i.kcBuilder(KubeconfigPath, i.log) + if err != nil { + i.log.Error(err) + return err + } + if err := i.StorePublicKeyInControlPlane(publicKeyBytes, kc); err != nil { + return errors.Wrap(err, "unable to store public key for node in control plane") + } i.log.Info("Setting bootstrap node new role to master") } @@ -572,7 +612,7 @@ func (i *installer) workerWaitFor2ReadyMasters(ctx context.Context) error { return false } } - if swag.StringValue(cluster.Kind) == models.ClusterKindAddHostsCluster { + if convert.StringValue(cluster.Kind) == models.ClusterKindAddHostsCluster { return true } diff --git a/src/installer/installer_test.go b/src/installer/installer_test.go index a87e402db2..3212c696b8 100644 --- a/src/installer/installer_test.go +++ b/src/installer/installer_test.go @@ -310,6 +310,8 @@ var _ = Describe("installer HostRoleMaster role", func() { for _, version := range []string{"4.7", "4.7.1", "4.7-pre-release", "4.8"} { Context(version, func() { BeforeEach(func() { + mockk8sclient.EXPECT().CreateNamespace("node-public-keys").Times(1) + mockk8sclient.EXPECT().CreateConfigMap(gomock.Any(), "node-public-keys", gomock.Any()) conf.OpenshiftVersion = version }) AfterEach(func() { @@ -442,6 +444,8 @@ var _ = Describe("installer HostRoleMaster role", func() { Expect(ret).To(HaveOccurred()) }) It("bootstrap role extract ignition retry", func() { + mockk8sclient.EXPECT().CreateNamespace("node-public-keys").Times(1) + mockk8sclient.EXPECT().CreateConfigMap(gomock.Any(), "node-public-keys", gomock.Any()) updateProgressSuccess([][]string{{string(models.HostStageStartingInstallation), conf.Role}, {string(models.HostStageWaitingForControlPlane), waitingForBootstrapToPrepare}, {string(models.HostStageWaitingForControlPlane), waitingForMastersStatusInfo}, @@ -554,6 +558,8 @@ var _ = Describe("installer HostRoleMaster role", func() { }) It(fmt.Sprintf("for platform type %v is expected to remove uninitialized taint = %v", platformType, expectedRemoveUninitializedTaint), func() { + mockk8sclient.EXPECT().CreateNamespace("node-public-keys").Times(1) + mockk8sclient.EXPECT().CreateConfigMap(gomock.Any(), "node-public-keys", gomock.Any()) updateProgressSuccess([][]string{{string(models.HostStageStartingInstallation), conf.Role}, {string(models.HostStageWaitingForControlPlane), waitingForBootstrapToPrepare}, {string(models.HostStageWaitingForControlPlane), waitingForMastersStatusInfo}, @@ -894,6 +900,8 @@ var _ = Describe("installer HostRoleMaster role", func() { evaluateDiskSymlinkSuccess() }) It("worker role happy flow", func() { + mockk8sclient.EXPECT().CreateNamespace("node-public-keys").Times(1) + mockk8sclient.EXPECT().CreateConfigMap(gomock.Any(), "node-public-keys", gomock.Any()) updateProgressSuccess([][]string{{string(models.HostStageStartingInstallation), conf.Role}, {string(models.HostStageInstalling), conf.Role}, {string(models.HostStageWritingImageToDisk)}, diff --git a/src/inventory_client/inventory_client.go b/src/inventory_client/inventory_client.go index 7cebb643e7..b997a58219 100644 --- a/src/inventory_client/inventory_client.go +++ b/src/inventory_client/inventory_client.go @@ -20,8 +20,8 @@ import ( ttlCache "github.com/ReneKroon/ttlcache/v2" "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" "github.com/hashicorp/go-version" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/utils" "github.com/openshift/assisted-service/client" "github.com/openshift/assisted-service/client/events" @@ -254,7 +254,7 @@ func (c *inventoryClient) UploadIngressCa(ctx context.Context, ingressCA string, } func (c *inventoryClient) GetCluster(ctx context.Context, withHosts bool) (*models.Cluster, error) { - cluster, err := c.ai.Installer.V2GetCluster(ctx, &installer.V2GetClusterParams{ClusterID: c.clusterId, ExcludeHosts: swag.Bool(!withHosts)}) + cluster, err := c.ai.Installer.V2GetCluster(ctx, &installer.V2GetClusterParams{ClusterID: c.clusterId, ExcludeHosts: convert.Bool(!withHosts)}) if err != nil { return nil, err } @@ -263,7 +263,7 @@ func (c *inventoryClient) GetCluster(ctx context.Context, withHosts bool) (*mode } func (c *inventoryClient) ListsHostsForRole(ctx context.Context, role string) (models.HostList, error) { - ret, err := c.ai.Installer.ListClusterHosts(ctx, &installer.ListClusterHostsParams{ClusterID: c.clusterId, Role: swag.String(role)}) + ret, err := c.ai.Installer.ListClusterHosts(ctx, &installer.ListClusterHostsParams{ClusterID: c.clusterId, Role: convert.String(role)}) if err != nil { return nil, err } diff --git a/src/k8s_client/k8s_client.go b/src/k8s_client/k8s_client.go index 79dd447665..7c54a388d7 100644 --- a/src/k8s_client/k8s_client.go +++ b/src/k8s_client/k8s_client.go @@ -88,6 +88,8 @@ type K8SClient interface { IsClusterCapabilityEnabled(configv1.ClusterVersionCapability) (bool, error) UntaintNode(name string) error PatchMachineConfigPoolPaused(pause bool, mcpName string) error + CreateNamespace(name string) error + CreateConfigMap(name string, namespace string, data map[string]string) error } type K8SClientBuilder func(configPath string, logger logrus.FieldLogger) (K8SClient, error) @@ -691,3 +693,27 @@ func (c *k8sClient) PatchMachineConfigPoolPaused(pause bool, mcpName string) err c.log.Infof("Setting pause MCP %s to %t", mcpName, pause) return c.runtimeClient.Patch(context.TODO(), mcp, runtimeclient.RawPatch(types.MergePatchType, pausePatch)) } + +func (c *k8sClient) CreateNamespace(name string) error { + if _, err := c.client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }, metav1.CreateOptions{}); err != nil { + return errors.Wrap(err, "unable to create namespace for known hosts") + } + return nil +} + +func (c *k8sClient) CreateConfigMap(name string, namespace string, data map[string]string) error { + if _, err := c.client.CoreV1().ConfigMaps(namespace).Create(context.TODO(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Data: data, + }, metav1.CreateOptions{}); err != nil { + return errors.Wrap(err, "unable to create config map for known hosts") + } + return nil +} diff --git a/src/k8s_client/mock_k8s_client.go b/src/k8s_client/mock_k8s_client.go index 45d0d1c9a1..cebeec2841 100644 --- a/src/k8s_client/mock_k8s_client.go +++ b/src/k8s_client/mock_k8s_client.go @@ -61,6 +61,20 @@ func (mr *MockK8SClientMockRecorder) ApproveCsr(csr any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApproveCsr", reflect.TypeOf((*MockK8SClient)(nil).ApproveCsr), csr) } +// CreateConfigMap mocks base method. +func (m *MockK8SClient) CreateConfigMap(name, namespace string, data map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateConfigMap", name, namespace, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateConfigMap indicates an expected call of CreateConfigMap. +func (mr *MockK8SClientMockRecorder) CreateConfigMap(name, namespace, data interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateConfigMap", reflect.TypeOf((*MockK8SClient)(nil).CreateConfigMap), name, namespace, data) +} + // CreateEvent mocks base method. func (m *MockK8SClient) CreateEvent(namespace, name, message, component string) (*v12.Event, error) { m.ctrl.T.Helper() @@ -76,6 +90,20 @@ func (mr *MockK8SClientMockRecorder) CreateEvent(namespace, name, message, compo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEvent", reflect.TypeOf((*MockK8SClient)(nil).CreateEvent), namespace, name, message, component) } +// CreateNamespace mocks base method. +func (m *MockK8SClient) CreateNamespace(name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNamespace", name) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNamespace indicates an expected call of CreateNamespace. +func (mr *MockK8SClientMockRecorder) CreateNamespace(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNamespace", reflect.TypeOf((*MockK8SClient)(nil).CreateNamespace), name) +} + // DeleteInstallPlan mocks base method. func (m *MockK8SClient) DeleteInstallPlan(installPlan types.NamespacedName) error { m.ctrl.T.Helper() diff --git a/src/main/assisted-installer-controller/assisted_installer_main_test.go b/src/main/assisted-installer-controller/assisted_installer_main_test.go index 450820f508..3fcaa71b8d 100644 --- a/src/main/assisted-installer-controller/assisted_installer_main_test.go +++ b/src/main/assisted-installer-controller/assisted_installer_main_test.go @@ -8,10 +8,10 @@ import ( "testing" "time" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/k8s_client" v1 "k8s.io/api/core/v1" - "github.com/go-openapi/swag" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" configv1 "github.com/openshift/api/config/v1" @@ -64,21 +64,21 @@ var _ = Describe("installer HostRoleMaster role", func() { It("Waiting for cluster installed - first cluster error then installed", func() { // fail to connect to assisted and then succeed mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, fmt.Errorf("dummy")).Times(1) - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) }) It("Waiting for cluster cancelled", func() { - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusCancelled)}, + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusCancelled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) }) It("Waiting for cluster error - should set error status", func() { - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusError)}, + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusError)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(true)) @@ -91,7 +91,7 @@ var _ = Describe("installer HostRoleMaster role", func() { } mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, installer.NewV2GetClusterUnauthorized()).Times(maximumErrorsBeforeExit) // added to make waitForInstallation exit - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, nil).Times(1) + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) Expect(exitCode).Should(Equal(0)) @@ -105,7 +105,7 @@ var _ = Describe("installer HostRoleMaster role", func() { mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, installer.NewV2GetClusterNotFound()).Times(maximumErrorsBeforeExit) // added to make waitForInstallation exit - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, nil).Times(1) + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) @@ -119,7 +119,7 @@ var _ = Describe("installer HostRoleMaster role", func() { } mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, installer.NewV2GetClusterNotFound()).Times(1) // added to make waitForInstallation exit - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, nil).Times(1) + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) @@ -133,7 +133,7 @@ var _ = Describe("installer HostRoleMaster role", func() { } mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, installer.NewV2GetClusterUnauthorized()).Times(maximumErrorsBeforeExit) // added to make waitForInstallation exit - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, nil).Times(1) + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) @@ -147,7 +147,7 @@ var _ = Describe("installer HostRoleMaster role", func() { } mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(nil, installer.NewV2GetClusterUnauthorized()).Times(maximumErrorsBeforeExit - 2) // added to make waitForInstallation exit - mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: swag.String(models.ClusterStatusInstalled)}, nil).Times(1) + mockbmclient.EXPECT().GetCluster(gomock.Any(), false).Return(&models.Cluster{Status: convert.String(models.ClusterStatusInstalled)}, nil).Times(1) waitForInstallation(mockbmclient, l, mockController) Expect(status.HasError()).Should(Equal(false)) diff --git a/src/ops/ops.go b/src/ops/ops.go index bfa4f2c266..379077793d 100644 --- a/src/ops/ops.go +++ b/src/ops/ops.go @@ -22,7 +22,6 @@ import ( "time" config_latest "github.com/coreos/ignition/v2/config/v3_2" - "github.com/go-openapi/swag" mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" "github.com/thoas/go-funk" "github.com/vincent-petithory/dataurl" @@ -33,6 +32,7 @@ import ( "github.com/sirupsen/logrus" "github.com/openshift/assisted-installer/src/config" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/ops/execute" "github.com/openshift/assisted-installer/src/utils" ) @@ -593,7 +593,7 @@ func (o *ops) GetMustGatherLogs(workDir, kubeconfigPath string, images ...string } if len(files) == 0 { - lerr := fmt.Errorf("Failed to find must-gather output") + lerr := fmt.Errorf("failed to find must-gather output") o.log.Errorf(lerr.Error()) return "", lerr } @@ -707,7 +707,7 @@ func (o *ops) getPointedIgnitionAndCA(ignitionPath string) (string, string, erro for i := range conf.Ignition.Config.Merge { r := &conf.Ignition.Config.Merge[i] if r.Source != nil { - source = swag.StringValue(r.Source) + source = convert.StringValue(r.Source) if source != "" { break } @@ -720,7 +720,7 @@ func (o *ops) getPointedIgnitionAndCA(ignitionPath string) (string, string, erro for i := range conf.Ignition.Security.TLS.CertificateAuthorities { r := &conf.Ignition.Security.TLS.CertificateAuthorities[i] if r.Source != nil { - d, err := dataurl.DecodeString(swag.StringValue(r.Source)) + d, err := dataurl.DecodeString(convert.StringValue(r.Source)) if err != nil { return "", "", err } diff --git a/src/ops/ops_test.go b/src/ops/ops_test.go index b0692a09f9..b1bfb7efe5 100644 --- a/src/ops/ops_test.go +++ b/src/ops/ops_test.go @@ -12,10 +12,10 @@ import ( "os" "reflect" + "github.com/openshift/assisted-installer/src/convert" "github.com/openshift/assisted-installer/src/ops/execute" "github.com/coreos/ignition/v2/config/v3_2/types" - "github.com/go-openapi/swag" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" @@ -192,11 +192,11 @@ WkBKOclmOV2xlTVuPw== ret.Ignition.Version = "3.2.0" ret.Ignition.Config.Merge = append(ret.Ignition.Config.Merge, types.Resource{ - Source: swag.String(source), + Source: convert.String(source), }) ret.Ignition.Security.TLS.CertificateAuthorities = append(ret.Ignition.Security.TLS.CertificateAuthorities, types.Resource{ - Source: swag.String(dataurl.EncodeBytes(localhostCert)), + Source: convert.String(dataurl.EncodeBytes(localhostCert)), }) return ret }