diff --git a/controllers/falcon_container/falconcontainer_controller.go b/controllers/falcon_container/falconcontainer_controller.go index 7e28b12c..04013945 100644 --- a/controllers/falcon_container/falconcontainer_controller.go +++ b/controllers/falcon_container/falconcontainer_controller.go @@ -7,6 +7,9 @@ import ( "time" falconv1alpha1 "github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1" + k8sutils "github.com/crowdstrike/falcon-operator/internal/controller/common" + "github.com/crowdstrike/falcon-operator/pkg/aws" + "github.com/crowdstrike/falcon-operator/pkg/common" "github.com/crowdstrike/falcon-operator/version" "github.com/go-logr/logr" arv1 "k8s.io/api/admissionregistration/v1" @@ -117,7 +120,7 @@ func (r *FalconContainerReconciler) Reconcile(ctx context.Context, req ctrl.Requ } else { switch falconContainer.Spec.Registry.Type { case falconv1alpha1.RegistryTypeECR: - if _, err := r.UpsertECRRepo(ctx); err != nil { + if _, err := aws.UpsertECRRepo(ctx, "falcon-container"); err != nil { err = r.StatusUpdate(ctx, req, log, falconContainer, falconv1alpha1.ConditionFailed, metav1.ConditionFalse, "Reconciling", fmt.Sprintf("failed to reconcile ECR repository: %v", err)) if err != nil { return ctrl.Result{}, err @@ -239,7 +242,7 @@ func (r *FalconContainerReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, fmt.Errorf("failed to reconcile injector Service: %v", err) } - pod, err := r.injectorPodReady(ctx, falconContainer) + pod, err := k8sutils.GetReadyPod(r.Client, ctx, r.Namespace(), map[string]string{common.FalconComponentKey: common.FalconSidecarSensor}) if err != nil && err.Error() != "No Injector pod found in a Ready state" { err = r.StatusUpdate(ctx, req, log, falconContainer, falconv1alpha1.ConditionFailed, metav1.ConditionFalse, "Reconciling", fmt.Sprintf("failed to find Ready injector pod: %v", err)) if err != nil { diff --git a/controllers/falcon_container/image_push.go b/controllers/falcon_container/image_push.go index 5f2270ce..ac21822d 100644 --- a/controllers/falcon_container/image_push.go +++ b/controllers/falcon_container/image_push.go @@ -9,6 +9,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" falconv1alpha1 "github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1" + "github.com/crowdstrike/falcon-operator/internal/controller/image" + "github.com/crowdstrike/falcon-operator/pkg/aws" "github.com/crowdstrike/falcon-operator/pkg/gcp" "github.com/crowdstrike/falcon-operator/pkg/k8s_utils" "github.com/crowdstrike/falcon-operator/pkg/registry/auth" @@ -33,7 +35,7 @@ func (r *FalconContainerReconciler) PushImage(ctx context.Context, log logr.Logg } log.Info("Found secret for image push", "Secret.Name", pushAuth.Name()) - image := NewImageRefresher(ctx, log, r.falconApiConfig(ctx, falconContainer), pushAuth, falconContainer.Spec.Registry.TLS.InsecureSkipVerify) + image := image.NewImageRefresher(ctx, log, r.falconApiConfig(ctx, falconContainer), pushAuth, falconContainer.Spec.Registry.TLS.InsecureSkipVerify) version := falconContainer.Spec.Version // If we have version locking enabled (as it is by default), use the already configured version if present @@ -113,7 +115,7 @@ func (r *FalconContainerReconciler) registryUri(ctx context.Context, falconConta return "gcr.io/" + projectId + "/falcon-container", nil case falconv1alpha1.RegistryTypeECR: - repo, err := r.UpsertECRRepo(ctx) + repo, err := aws.UpsertECRRepo(ctx, "falcon-container") if err != nil { return "", fmt.Errorf("Cannot get target docker URI for ECR repository: %v", err) } diff --git a/controllers/falcon_container/injector.go b/controllers/falcon_container/injector.go index cdb91687..e8c1b314 100644 --- a/controllers/falcon_container/injector.go +++ b/controllers/falcon_container/injector.go @@ -10,7 +10,6 @@ import ( "github.com/go-logr/logr" "github.com/operator-framework/operator-lib/proxy" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/crowdstrike/falcon-operator/pkg/common" "github.com/crowdstrike/falcon-operator/pkg/tls" @@ -37,7 +36,13 @@ func (r *FalconContainerReconciler) reconcileInjectorTLSSecret(ctx context.Conte if falconContainer.Spec.Injector.TLS.Validity != nil { validity = *falconContainer.Spec.Injector.TLS.Validity } - c, k, b, err := tls.CertSetup(validity) + + certInfo := tls.CertInfo{ + CommonName: fmt.Sprintf("%s.%s.svc", injectorName, r.Namespace()), + DNSNames: []string{fmt.Sprintf("%s.%s.svc", injectorName, r.Namespace()), fmt.Sprintf("%s.%s.svc.cluster.local", injectorName, r.Namespace())}, + } + + c, k, b, err := tls.CertSetup(validity, certInfo) if err != nil { return &corev1.Secret{}, fmt.Errorf("failed to generate Falcon Container PKI: %v", err) } @@ -148,25 +153,3 @@ func (r *FalconContainerReconciler) reconcileDeployment(ctx context.Context, log return existingDeployment, nil } - -func (r *FalconContainerReconciler) injectorPodReady(ctx context.Context, falconContainer *falconv1alpha1.FalconContainer) (*corev1.Pod, error) { - podList := &corev1.PodList{} - listOpts := []client.ListOption{ - client.InNamespace(r.Namespace()), - client.MatchingLabels{common.FalconComponentKey: common.FalconSidecarSensor}, - } - - if err := r.List(ctx, podList, listOpts...); err != nil { - return nil, fmt.Errorf("unable to list pods: %v", err) - } - - for _, pod := range podList.Items { - for _, cond := range pod.Status.Conditions { - if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue { - return &pod, nil - } - } - } - - return &corev1.Pod{}, fmt.Errorf("No Injector pod found in a Ready state") -} diff --git a/internal/controller/common/utils.go b/internal/controller/common/utils.go new file mode 100644 index 00000000..c0b93548 --- /dev/null +++ b/internal/controller/common/utils.go @@ -0,0 +1,31 @@ +package common + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func GetReadyPod(r client.Client, ctx context.Context, namespace string, matchingLabels client.MatchingLabels) (*corev1.Pod, error) { + podList := &corev1.PodList{} + listOpts := []client.ListOption{ + client.InNamespace(namespace), + matchingLabels, + } + + if err := r.List(ctx, podList, listOpts...); err != nil { + return nil, fmt.Errorf("unable to list pods: %v", err) + } + + for _, pod := range podList.Items { + for _, cond := range pod.Status.Conditions { + if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue { + return &pod, nil + } + } + } + + return &corev1.Pod{}, fmt.Errorf("No Injector pod found in a Ready state") +} diff --git a/internal/controller/common/utils_test.go b/internal/controller/common/utils_test.go new file mode 100644 index 00000000..805d0c79 --- /dev/null +++ b/internal/controller/common/utils_test.go @@ -0,0 +1 @@ +package common diff --git a/internal/controller/image/image_refresher.go b/internal/controller/image/image_refresher.go new file mode 100644 index 00000000..30962b3d --- /dev/null +++ b/internal/controller/image/image_refresher.go @@ -0,0 +1,132 @@ +package image + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/go-logr/logr" + + "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + + "github.com/crowdstrike/falcon-operator/pkg/registry/auth" + "github.com/crowdstrike/falcon-operator/pkg/registry/falcon_registry" + "github.com/crowdstrike/gofalcon/falcon" +) + +type ImageRefresher struct { + ctx context.Context + log logr.Logger + falconConfig *falcon.ApiConfig + insecureSkipTLSVerify bool + pushCredentials auth.Credentials +} + +func NewImageRefresher(ctx context.Context, log logr.Logger, falconConfig *falcon.ApiConfig, pushAuth auth.Credentials, insecureSkipTLSVerify bool) *ImageRefresher { + return &ImageRefresher{ + ctx: ctx, + log: log, + falconConfig: falconConfig, + insecureSkipTLSVerify: insecureSkipTLSVerify, + pushCredentials: pushAuth, + } +} + +func (r *ImageRefresher) Refresh(imageDestination string, versionRequested *string) (string, error) { + falconTag, srcRef, sourceCtx, err := r.source(versionRequested) + if err != nil { + return "", err + } + + r.log.Info("Identified the latest Falcon Container image", "reference", srcRef.DockerReference().String()) + + policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}} + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return "", fmt.Errorf("Error loading trust policy: %v", err) + } + defer func() { _ = policyContext.Destroy() }() + + destinationCtx, err := r.destinationContext(r.insecureSkipTLSVerify) + if err != nil { + return "", err + } + + // Push to the registry with the falconTag + dest := fmt.Sprintf("docker://%s:%s", imageDestination, falconTag) + destRef, err := alltransports.ParseImageName(dest) + + if err != nil { + return "", fmt.Errorf("Invalid destination name %s: %v", dest, err) + } + + r.log.Info("Identified the target location for image push", "reference", destRef.DockerReference().String()) + _, err = copy.Image(r.ctx, policyContext, destRef, srcRef, + ©.Options{ + ReportWriter: os.Stdout, + SourceCtx: sourceCtx, + DestinationCtx: destinationCtx, + }, + ) + if err != nil { + return "", wrapWithHint(err) + } + + // Push to the registry with the latest tag + dest = fmt.Sprintf("docker://%s", imageDestination) + destRef, err = alltransports.ParseImageName(dest) + if err != nil { + return "", fmt.Errorf("Invalid destination name %s: %v", dest, err) + } + + r.log.Info("Identified the target location for image push", "reference", destRef.DockerReference().String()) + _, err = copy.Image(r.ctx, policyContext, destRef, srcRef, + ©.Options{ + ReportWriter: os.Stdout, + SourceCtx: sourceCtx, + DestinationCtx: destinationCtx, + }, + ) + + return falconTag, wrapWithHint(err) +} + +func (r *ImageRefresher) source(versionRequested *string) (falconTag string, falconImage types.ImageReference, systemContext *types.SystemContext, err error) { + registry, err := falcon_registry.NewFalconRegistry(r.ctx, r.falconConfig) + if err != nil { + return + } + + return registry.PullInfo(r.ctx, versionRequested) +} + +func (r *ImageRefresher) destinationContext(insecureSkipTLSVerify bool) (*types.SystemContext, error) { + ctx, err := r.pushCredentials.DestinationContext() + if err != nil { + return nil, err + } + + if insecureSkipTLSVerify { + ctx.DockerInsecureSkipTLSVerify = 1 + } + + return ctx, nil +} + +func wrapWithHint(in error) error { + // Use of credentials store outside of docker command is somewhat limited + // See https://github.com/moby/moby/issues/39377 + // https://github.com/containers/image/pull/656 + if in == nil { + return in + } + + if strings.Contains(in.Error(), "authentication required") { + return fmt.Errorf("Could not authenticate to the registry: %w", in) + } + return in +} diff --git a/pkg/aws/ecr.go b/pkg/aws/ecr.go index 9f701861..af18ea3d 100644 --- a/pkg/aws/ecr.go +++ b/pkg/aws/ecr.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/ecr" + "github.com/aws/aws-sdk-go-v2/service/ecr/types" ecr_types "github.com/aws/aws-sdk-go-v2/service/ecr/types" ) @@ -41,3 +42,17 @@ func (c *Config) ECRLogin(ctx context.Context) ([]byte, error) { } return base64.StdEncoding.DecodeString(*output.AuthorizationData[0].AuthorizationToken) } + +func UpsertECRRepo(ctx context.Context, name string) (*types.Repository, error) { + cfg, err := NewConfig() + if err != nil { + return nil, fmt.Errorf("Failed to initialise connection to AWS. Please make sure that kubernetes service account falcon-operator has access to AWS IAM role and OIDC Identity provider is running on the cluster. Error was: %v", err) + } + + data, err := cfg.UpsertRepository(ctx, name) + if err != nil { + return nil, fmt.Errorf("Failed to upsert ECR repository: %v", err) + } + + return data, nil +} diff --git a/pkg/tls/certs.go b/pkg/tls/certs.go index bdb1f728..b2c230d3 100644 --- a/pkg/tls/certs.go +++ b/pkg/tls/certs.go @@ -11,8 +11,13 @@ import ( "time" ) +type CertInfo struct { + CommonName string + DNSNames []string +} + // CertSetup will generate and return tls certs -func CertSetup(days int) ([]byte, []byte, []byte, error) { +func CertSetup(days int, certInfo CertInfo) ([]byte, []byte, []byte, error) { // set up our CA certificate ca := &x509.Certificate{ SerialNumber: new(big.Int).Lsh(big.NewInt(1), 128), @@ -60,14 +65,14 @@ func CertSetup(days int) ([]byte, []byte, []byte, error) { cert := &x509.Certificate{ SerialNumber: new(big.Int).Lsh(big.NewInt(1), 128), Subject: pkix.Name{ - CommonName: "falcon-sidecar-injector.falcon-system.svc", + CommonName: certInfo.CommonName, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, days), SubjectKeyId: []byte("234567"), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, - DNSNames: []string{"falcon-sidecar-injector.falcon-system.svc", "falcon-sidecar-injector.falcon-system.svc.cluster.local"}, + DNSNames: certInfo.DNSNames, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)