diff --git a/api/v1alpha1/humiobootstraptoken_types.go b/api/v1alpha1/humiobootstraptoken_types.go index 57d0cdd96..d78e72e73 100644 --- a/api/v1alpha1/humiobootstraptoken_types.go +++ b/api/v1alpha1/humiobootstraptoken_types.go @@ -43,22 +43,22 @@ type HumioBootstrapTokenSpec struct { } type HumioTokenSecretSpec struct { - // TODO: we could clean this up by removing the "CreateIfMissing" and in docs explain if you want to use your own secret + // TODO: we could clean this up by removing the "AutoCreate" and in docs explain if you want to use your own secret // then create the secret before the bootstraptoken resource - CreateIfMissing *bool `json:"createIfMissing,omitempty"` - SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` + AutoCreate *bool `json:"autoCreate,omitempty"` + SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` } type HumioHashedTokenSecretSpec struct { - // TODO: maybe remove CreateIfMissing - CreateIfMissing *bool `json:"createIfMissing,omitempty"` - SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` + // TODO: maybe remove AutoCreate + AutoCreate *bool `json:"autoCreate,omitempty"` + SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` } type HumioBootstrapTokenStatus struct { // TODO set the status. This is used by the HumioCluster resource to get the secret reference and load the secret. We don't want to rely on the spec // here as the spec could be empty. Or do we want to - Created bool `json:"created,omitempty"` + //Created bool `json:"created,omitempty"` TokenSecretKeyRef HumioTokenSecretStatus `json:"tokenSecretStatus,omitempty"` HashedTokenSecretKeyRef HumioHashedTokenSecretStatus `json:"hashedTokenSecretStatus,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8a62bf991..36e902c86 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -812,8 +812,8 @@ func (in *HumioExternalClusterStatus) DeepCopy() *HumioExternalClusterStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HumioHashedTokenSecretSpec) DeepCopyInto(out *HumioHashedTokenSecretSpec) { *out = *in - if in.CreateIfMissing != nil { - in, out := &in.CreateIfMissing, &out.CreateIfMissing + if in.AutoCreate != nil { + in, out := &in.AutoCreate, &out.AutoCreate *out = new(bool) **out = **in } @@ -1485,8 +1485,8 @@ func (in *HumioRetention) DeepCopy() *HumioRetention { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HumioTokenSecretSpec) DeepCopyInto(out *HumioTokenSecretSpec) { *out = *in - if in.CreateIfMissing != nil { - in, out := &in.CreateIfMissing, &out.CreateIfMissing + if in.AutoCreate != nil { + in, out := &in.AutoCreate, &out.AutoCreate *out = new(bool) **out = **in } diff --git a/charts/humio-operator/crds/core.humio.com_humiobootstraptokens.yaml b/charts/humio-operator/crds/core.humio.com_humiobootstraptokens.yaml index b16b0b0f7..870787af5 100644 --- a/charts/humio-operator/crds/core.humio.com_humiobootstraptokens.yaml +++ b/charts/humio-operator/crds/core.humio.com_humiobootstraptokens.yaml @@ -56,8 +56,8 @@ spec: type: string hashedTokenSecret: properties: - createIfMissing: - description: 'TODO: maybe remove CreateIfMissing' + autoCreate: + description: 'TODO: maybe remove AutoCreate' type: boolean secretKeyRef: description: SecretKeySelector selects a key of a Secret. @@ -86,8 +86,8 @@ spec: type: string tokenSecret: properties: - createIfMissing: - description: 'TODO: we could clean this up by removing the "CreateIfMissing" + autoCreate: + description: 'TODO: we could clean this up by removing the "AutoCreate" and in docs explain if you want to use your own secret then create the secret before the bootstraptoken resource' type: boolean @@ -113,12 +113,6 @@ spec: type: object status: properties: - created: - description: TODO set the status. This is used by the HumioCluster - resource to get the secret reference and load the secret. We don't - want to rely on the spec here as the spec could be empty. Or do - we want to - type: boolean hashedTokenSecretStatus: properties: secretKeyRef: @@ -141,6 +135,10 @@ spec: type: object type: object tokenSecretStatus: + description: TODO set the status. This is used by the HumioCluster + resource to get the secret reference and load the secret. We don't + want to rely on the spec here as the spec could be empty. Or do + we want to Created bool `json:"created,omitempty"` properties: secretKeyRef: description: SecretKeySelector selects a key of a Secret. diff --git a/config/crd/bases/core.humio.com_humiobootstraptokens.yaml b/config/crd/bases/core.humio.com_humiobootstraptokens.yaml index b16b0b0f7..870787af5 100644 --- a/config/crd/bases/core.humio.com_humiobootstraptokens.yaml +++ b/config/crd/bases/core.humio.com_humiobootstraptokens.yaml @@ -56,8 +56,8 @@ spec: type: string hashedTokenSecret: properties: - createIfMissing: - description: 'TODO: maybe remove CreateIfMissing' + autoCreate: + description: 'TODO: maybe remove AutoCreate' type: boolean secretKeyRef: description: SecretKeySelector selects a key of a Secret. @@ -86,8 +86,8 @@ spec: type: string tokenSecret: properties: - createIfMissing: - description: 'TODO: we could clean this up by removing the "CreateIfMissing" + autoCreate: + description: 'TODO: we could clean this up by removing the "AutoCreate" and in docs explain if you want to use your own secret then create the secret before the bootstraptoken resource' type: boolean @@ -113,12 +113,6 @@ spec: type: object status: properties: - created: - description: TODO set the status. This is used by the HumioCluster - resource to get the secret reference and load the secret. We don't - want to rely on the spec here as the spec could be empty. Or do - we want to - type: boolean hashedTokenSecretStatus: properties: secretKeyRef: @@ -141,6 +135,10 @@ spec: type: object type: object tokenSecretStatus: + description: TODO set the status. This is used by the HumioCluster + resource to get the secret reference and load the secret. We don't + want to rely on the spec here as the spec could be empty. Or do + we want to Created bool `json:"created,omitempty"` properties: secretKeyRef: description: SecretKeySelector selects a key of a Secret. diff --git a/controllers/humiobootstraptoken_controller.go b/controllers/humiobootstraptoken_controller.go index e733d9973..923184bcc 100644 --- a/controllers/humiobootstraptoken_controller.go +++ b/controllers/humiobootstraptoken_controller.go @@ -45,8 +45,10 @@ import ( const ( // BootstrapTokenSecretHashedTokenName is the name of the hashed token key inside the bootstrap token secret BootstrapTokenSecretHashedTokenName = "hashedToken" - // BootstrapTokenSecretName is the name of the secret key inside the bootstrap token secret - BootstrapTokenSecretName = "secret" + // BootstrapTokenSecretSecretName is the name of the secret key inside the bootstrap token secret + BootstrapTokenSecretSecretName = "secret" + // BootstrapTokenSecretPassphraseKey is the key name for the passphrase set in the bootstrap token secret + BootstrapTokenSecretPassphraseKey = "passphrase" ) // HumioBootstrapTokenReconciler reconciles a HumioBootstrapToken object @@ -112,36 +114,31 @@ func (r *HumioBootstrapTokenReconciler) Reconcile(ctx context.Context, req ctrl. return reconcile.Result{}, err } - if err := r.Get(ctx, req.NamespacedName, hbt); err != nil { + if err := r.updateStatus(ctx, hbt); err != nil { return reconcile.Result{}, err } - // TODO: rather than status, should we set a default in the spec instead? + return reconcile.Result{RequeueAfter: time.Second * 60}, nil +} + +func (r *HumioBootstrapTokenReconciler) updateStatus(ctx context.Context, hbt *humiov1alpha1.HumioBootstrapToken) error { hbt.Status.TokenSecretKeyRef = humiov1alpha1.HumioTokenSecretStatus{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ - Name: fmt.Sprintf("%s-bootstrap-token", hbt.Name), + Name: fmt.Sprintf("%s-%s", hbt.Name, kubernetes.BootstrapTokenSecretNameSuffix), }, - Key: "secret", + Key: BootstrapTokenSecretSecretName, }, } hbt.Status.HashedTokenSecretKeyRef = humiov1alpha1.HumioHashedTokenSecretStatus{ SecretKeyRef: &corev1.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ - Name: fmt.Sprintf("%s-bootstrap-token", hbt.Name), + Name: fmt.Sprintf("%s-%s", hbt.Name, kubernetes.BootstrapTokenSecretNameSuffix), }, - Key: "hashedToken", + Key: BootstrapTokenSecretHashedTokenName, }, } - if err := r.Client.Status().Update(ctx, hbt); err != nil { - return reconcile.Result{}, err - } - - // TODO: take code from images/helper/main.go and use "secret" to create an admin user and store the token in a k8s secret. Update the - // HumioBootstrapToken Status to also include the admin token. - // Alternatively, the creation of the admin user could be handled by the humiocluster controller. Perhaps that is a better place for it? - - return reconcile.Result{RequeueAfter: time.Second * 60}, nil + return r.Client.Status().Update(ctx, hbt) } func (r *HumioBootstrapTokenReconciler) execCommand(pod *corev1.Pod, args []string) (string, error) { @@ -186,7 +183,7 @@ func (r *HumioBootstrapTokenReconciler) execCommand(pod *corev1.Pod, args []stri return "", err } var stdout, stderr bytes.Buffer - err = exec.Stream(remotecommand.StreamOptions{ + err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{ Stdin: nil, Stdout: &stdout, Stderr: &stderr, @@ -258,35 +255,46 @@ func (r *HumioBootstrapTokenReconciler) deletePod(ctx context.Context, hbt *humi func (r *HumioBootstrapTokenReconciler) ensureBootstrapTokenSecret(ctx context.Context, hbt *humiov1alpha1.HumioBootstrapToken, hc *humiov1alpha1.HumioCluster) error { r.Log.Info("ensuring bootstrap token secret") humioBootstrapTokenConfig := NewHumioBootstrapTokenConfig(hbt, hc) - if !humioBootstrapTokenConfig.tokenSecretCreateIfMissing() { - return nil - } if _, err := r.getBootstrapTokenSecret(ctx, hbt, hc); err != nil { if k8serrors.IsNotFound(err) { - // TODO: something better - //randomPass := kubernetes.RandomString() - secretData := map[string][]byte{ - //"passphrase": []byte(randomPass), - } - // TODO: make passphrase constant + secretData := map[string][]byte{} if hbt.Spec.TokenSecret.SecretKeyRef != nil { secret, err := kubernetes.GetSecret(ctx, r, hbt.Spec.TokenSecret.SecretKeyRef.Name, hbt.Namespace) if err != nil { return r.logErrorAndReturn(err, fmt.Sprintf("could not get secret %s", hbt.Spec.TokenSecret.SecretKeyRef.Name)) } - if passphrase, ok := secret.Data["passphrase"]; ok { - secretData["passphrase"] = passphrase + if secretValue, ok := secret.Data[hbt.Spec.TokenSecret.SecretKeyRef.Key]; ok { + secretData[BootstrapTokenSecretSecretName] = secretValue + } else { + return r.logErrorAndReturn(err, fmt.Sprintf("could not get value from secret %s. "+ + "secret does not contain value for key \"%s\"", hbt.Spec.TokenSecret.SecretKeyRef.Name, hbt.Spec.TokenSecret.SecretKeyRef.Key)) } + } - secret := kubernetes.ConstructSecret(hbt.Name, hbt.Namespace, humioBootstrapTokenConfig.bootstrapTokenName(), secretData, nil) - if err := controllerutil.SetControllerReference(hbt, secret, r.Scheme()); err != nil { - return r.logErrorAndReturn(err, "could not set controller reference") + if hbt.Spec.HashedTokenSecret.SecretKeyRef != nil { + secret, err := kubernetes.GetSecret(ctx, r, hbt.Spec.TokenSecret.SecretKeyRef.Name, hbt.Namespace) + if err != nil { + return r.logErrorAndReturn(err, fmt.Sprintf("could not get secret %s", hbt.Spec.TokenSecret.SecretKeyRef.Name)) + } + if hashedTokenValue, ok := secret.Data[hbt.Spec.HashedTokenSecret.SecretKeyRef.Key]; ok { + secretData[BootstrapTokenSecretHashedTokenName] = hashedTokenValue + } else { + return r.logErrorAndReturn(err, fmt.Sprintf("could not get value from secret %s. "+ + "secret does not contain value for key \"%s\"", hbt.Spec.HashedTokenSecret.SecretKeyRef.Name, hbt.Spec.HashedTokenSecret.SecretKeyRef.Key)) + } } - r.Log.Info(fmt.Sprintf("creating secret: %s", secret.Name)) - if err := r.Create(ctx, secret); err != nil { - return r.logErrorAndReturn(err, "could not create secret") + // TODO: do we really need autocreate option, or just assume create if there is no hbt.Spec.TokenSecret.SecretKeyRef set? + if humioBootstrapTokenConfig.autoCreate() { + secret := kubernetes.ConstructSecret(hbt.Name, hbt.Namespace, humioBootstrapTokenConfig.bootstrapTokenName(), secretData, nil) + if err := controllerutil.SetControllerReference(hbt, secret, r.Scheme()); err != nil { + return r.logErrorAndReturn(err, "could not set controller reference") + } + r.Log.Info(fmt.Sprintf("creating secret: %s", secret.Name)) + if err := r.Create(ctx, secret); err != nil { + return r.logErrorAndReturn(err, "could not create secret") + } + return nil } - return nil } else { return r.logErrorAndReturn(err, "could not get secret") } @@ -314,7 +322,7 @@ func (r *HumioBootstrapTokenReconciler) ensureBootstrapTokenHashedToken(ctx cont commandArgs := []string{"/bin/bash", "/app/humio/humio/bin/humio-run-class.sh", "com.humio.main.TokenHashing", "--json"} - if tokenSecret, ok := bootstrapTokenSecret.Data[BootstrapTokenSecretName]; ok { + if tokenSecret, ok := bootstrapTokenSecret.Data[BootstrapTokenSecretSecretName]; ok { commandArgs = append(commandArgs, string(tokenSecret)) } @@ -359,7 +367,7 @@ func (r *HumioBootstrapTokenReconciler) ensureBootstrapTokenHashedToken(ctx cont return err } // TODO: make tokenHash constant - updatedSecret.Data = map[string][]byte{BootstrapTokenSecretHashedTokenName: []byte(secretData.HashedToken), BootstrapTokenSecretName: []byte(secretData.Secret)} + updatedSecret.Data = map[string][]byte{BootstrapTokenSecretHashedTokenName: []byte(secretData.HashedToken), BootstrapTokenSecretSecretName: []byte(secretData.Secret)} if err = r.Update(ctx, updatedSecret); err != nil { return r.logErrorAndReturn(err, "failed to update secret with hashedToken data") diff --git a/controllers/humiobootstraptoken_defaults.go b/controllers/humiobootstraptoken_defaults.go index 8a5b95415..735c4a5e0 100644 --- a/controllers/humiobootstraptoken_defaults.go +++ b/controllers/humiobootstraptoken_defaults.go @@ -8,8 +8,8 @@ import ( ) const ( - BootstrapTokenSuffix = "bootstrap-token" - HashedBootstrapTokenSuffix = "hashed-bootstrap-token" + BootstrapTokenSuffix = "bootstrap-token" + //HashedBootstrapTokenSuffix = "hashed-bootstrap-token" ) type HumioBootstrapTokenConfig struct { @@ -28,17 +28,17 @@ func (b *HumioBootstrapTokenConfig) bootstrapTokenName() string { return fmt.Sprintf("%s-%s", b.BootstrapToken.Name, BootstrapTokenSuffix) } -func (b *HumioBootstrapTokenConfig) hashedBootstrapTokenName() string { - if b.BootstrapToken.Spec.HashedTokenSecret.SecretKeyRef != nil { - return b.BootstrapToken.Spec.HashedTokenSecret.SecretKeyRef.Name - } - return fmt.Sprintf("%s-%s", b.BootstrapToken.Name, HashedBootstrapTokenSuffix) -} +//func (b *HumioBootstrapTokenConfig) hashedBootstrapTokenName() string { +// if b.BootstrapToken.Spec.HashedTokenSecret.SecretKeyRef != nil { +// return b.BootstrapToken.Spec.HashedTokenSecret.SecretKeyRef.Name +// } +// return fmt.Sprintf("%s-%s", b.BootstrapToken.Name, HashedBootstrapTokenSuffix) +//} // TODO: remove this? -func (b *HumioBootstrapTokenConfig) tokenSecretCreateIfMissing() bool { - if b.BootstrapToken.Spec.TokenSecret.CreateIfMissing != nil { - return *b.BootstrapToken.Spec.TokenSecret.CreateIfMissing +func (b *HumioBootstrapTokenConfig) autoCreate() bool { + if b.BootstrapToken.Spec.TokenSecret.AutoCreate != nil { + return *b.BootstrapToken.Spec.TokenSecret.AutoCreate } return true } diff --git a/controllers/humiocluster_controller.go b/controllers/humiocluster_controller.go index 2b50e960a..5e691e64f 100644 --- a/controllers/humiocluster_controller.go +++ b/controllers/humiocluster_controller.go @@ -441,8 +441,19 @@ func (r *HumioClusterReconciler) ensureHumioClusterBootstrapToken(ctx context.Co Namespace: hc.Namespace, Name: hc.Name, } - hbt := &humiov1alpha1.HumioBootstrapToken{} - err := r.Client.Get(ctx, key, hbt) + //hbt := &humiov1alpha1.HumioBootstrapToken{} + hbtList := &humiov1alpha1.HumioBootstrapTokenList{} + var matchedHbt humiov1alpha1.HumioBootstrapToken + err := r.Client.List(ctx, hbtList) + if err != nil { + return r.logErrorAndReturn(err, "could not list HumioBootstrapToken") + } + for _, hbt := range hbtList.Items { + if hbt.Spec.ManagedClusterName == hc.Name { + matchedHbt = hbt + } + } + err = r.Client.Get(ctx, key, &matchedHbt) if err != nil { if k8serrors.IsNotFound(err) { hbt := &humiov1alpha1.HumioBootstrapToken{ @@ -470,13 +481,13 @@ func (r *HumioClusterReconciler) ensureHumioClusterBootstrapToken(ctx context.Co // }, }, } + if err := controllerutil.SetControllerReference(hc, hbt, r.Scheme()); err != nil { + return r.logErrorAndReturn(err, "could not set controller reference") + } err = r.Create(ctx, hbt) if err != nil { return r.logErrorAndReturn(err, "could not create bootstrap token resource") } - if err := controllerutil.SetControllerReference(hc, hbt, r.Scheme()); err != nil { - return r.logErrorAndReturn(err, "could not set controller reference") - } return nil } return r.logErrorAndReturn(err, "could not get bootstrap token resource") diff --git a/main.go b/main.go index eb4cdf097..c3e6ab3d9 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( "github.com/go-logr/logr" "github.com/go-logr/zapr" humioapi "github.com/humio/cli/api" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -184,6 +185,13 @@ func main() { ctrl.Log.Error(err, "unable to create controller", "controller", "HumioAlert") os.Exit(1) } + if err = (&controllers.HumioBootstrapTokenReconciler{ + Client: mgr.GetClient(), + BaseLogger: log, + }).SetupWithManager(mgr); err != nil { + ctrl.Log.Error(err, "unable to create controller", "controller", "HumioBootstrapToken") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/pkg/helpers/clusterinterface.go b/pkg/helpers/clusterinterface.go index 82547af7b..2d9f447b1 100644 --- a/pkg/helpers/clusterinterface.go +++ b/pkg/helpers/clusterinterface.go @@ -171,20 +171,33 @@ func (c Cluster) constructHumioConfig(ctx context.Context, k8sClient client.Clie var bootstrapToken corev1.Secret if withBootstrapToken { - // Get API token - err = k8sClient.Get(ctx, types.NamespacedName{ - Namespace: c.namespace, - //Name: fmt.Sprintf("%s-%s", c.managedClusterName, kubernetes.ServiceTokenSecretNameSuffix), - // TODO: pass in BootstrapTokenSuffix - Name: fmt.Sprintf("%s-bootstrap-token", c.managedClusterName), - }, &bootstrapToken) + hbtList := &humiov1alpha1.HumioBootstrapTokenList{} + var matchedHbt humiov1alpha1.HumioBootstrapToken + err := k8sClient.List(ctx, hbtList) if err != nil { - return nil, fmt.Errorf("unable to get bootstrap secret containing api token: %w", err) + return nil, fmt.Errorf("unable to get bootstrap token: %w", err) } - if _, ok := bootstrapToken.Data["secret"]; !ok { - return nil, fmt.Errorf("unable to get bootstrap secret containing api token. secret does not contain key named \"secret\"") + for _, hbt := range hbtList.Items { + if hbt.Spec.ManagedClusterName == c.managedClusterName { + matchedHbt = hbt + } + } + + // Get API token + if matchedHbt.Status.TokenSecretKeyRef.SecretKeyRef != nil { + err = k8sClient.Get(ctx, types.NamespacedName{ + Namespace: c.namespace, + Name: matchedHbt.Status.TokenSecretKeyRef.SecretKeyRef.Name, + }, &bootstrapToken) + if err != nil { + return nil, fmt.Errorf("unable to get bootstrap secret containing api token: %w", err) + } + if _, ok := bootstrapToken.Data[matchedHbt.Status.TokenSecretKeyRef.SecretKeyRef.Key]; !ok { + return nil, fmt.Errorf("unable to get bootstrap secret containing api token. secret does not contain key named \"%s\"", matchedHbt.Status.TokenSecretKeyRef.SecretKeyRef.Key) + } + config.Token = fmt.Sprintf("localroot~%s", string(bootstrapToken.Data[matchedHbt.Status.TokenSecretKeyRef.SecretKeyRef.Key])) } - config.Token = fmt.Sprintf("localroot~%s", string(bootstrapToken.Data["secret"])) + return nil, fmt.Errorf("unable to find bootstrap token with ManagedClusterName %s", c.managedClusterName) } // If we do not use TLS, return a client without CA certificate