From 4ae11ceff12aa1ada5a5ece1e3d45a3078abed68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:51:10 -0700 Subject: [PATCH 1/5] [auto] Update Java instrumentation to version v2.8.0 and .NET instrumentation to version v1.8.0 (#1497) This pull request updates the Java instrumentation to version v2.8.0 and the .NET instrumentation to version v1.8.0. It was created automatically by a GitHub Action. Co-authored-by: edeNFed <5587419+edeNFed@users.noreply.github.com> --- odiglet/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/odiglet/Dockerfile b/odiglet/Dockerfile index 8d59a507e..3c50c1509 100644 --- a/odiglet/Dockerfile +++ b/odiglet/Dockerfile @@ -61,7 +61,7 @@ RUN yarn --cwd ./odigos/agents/nodejs-native-community --production --frozen-loc FROM --platform=$BUILDPLATFORM busybox:1.36.1 AS dotnet-builder WORKDIR /dotnet-instrumentation -ARG DOTNET_OTEL_VERSION=v1.7.0 +ARG DOTNET_OTEL_VERSION=v1.8.0 ARG TARGETARCH RUN if [ "$TARGETARCH" = "arm64" ]; then \ echo "arm64" > /tmp/arch_suffix; \ @@ -93,7 +93,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ WORKDIR /instrumentations # Java -ARG JAVA_OTEL_VERSION=v2.7.0 +ARG JAVA_OTEL_VERSION=v2.8.0 ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$JAVA_OTEL_VERSION/opentelemetry-javaagent.jar /instrumentations/java/javaagent.jar RUN chmod 644 /instrumentations/java/javaagent.jar From 217e2891c4a236c5dec119fc6b802e3239fd01c7 Mon Sep 17 00:00:00 2001 From: Eden Federman Date: Tue, 1 Oct 2024 09:16:45 +0300 Subject: [PATCH 2/5] Mutating Webhook - Part 1 (#1560) * Implement basic webhook in instrumentor * Modify Helm and CLI to install new mutating webhook * Work with and without `cert-manager` --------- Co-authored-by: Amir Blum --- cli/cmd/resources/instrumentor.go | 262 +++++++++++++- cli/cmd/resources/owntelemetry.go | 8 + cli/go.mod | 19 +- cli/go.sum | 52 ++- cli/pkg/crypto/crypto.go | 326 ++++++++++++++++++ helm/odigos/templates/_helpers.tpl | 13 + .../templates/instrumentor/certificates.yaml | 36 ++ .../templates/instrumentor/deployment.yaml | 13 + .../templates/instrumentor/service.yaml | 12 + .../templates/instrumentor/webhook.yaml | 66 ++++ instrumentor/PROJECT | 7 + .../instrumentationdevice/manager.go | 9 + .../instrumentationdevice/pods_webhook.go | 34 ++ .../instrumentationdevice/suite_test.go | 8 + 14 files changed, 821 insertions(+), 44 deletions(-) create mode 100644 cli/pkg/crypto/crypto.go create mode 100644 helm/odigos/templates/_helpers.tpl create mode 100644 helm/odigos/templates/instrumentor/certificates.yaml create mode 100644 helm/odigos/templates/instrumentor/service.yaml create mode 100644 helm/odigos/templates/instrumentor/webhook.yaml create mode 100644 instrumentor/controllers/instrumentationdevice/pods_webhook.go diff --git a/cli/cmd/resources/instrumentor.go b/cli/cmd/resources/instrumentor.go index 29e3fe223..358a18c6a 100644 --- a/cli/cmd/resources/instrumentor.go +++ b/cli/cmd/resources/instrumentor.go @@ -2,13 +2,18 @@ package resources import ( "context" + "fmt" "github.com/odigos-io/odigos/cli/cmd/resources/resourcemanager" "github.com/odigos-io/odigos/cli/pkg/containers" + "github.com/odigos-io/odigos/cli/pkg/crypto" "github.com/odigos-io/odigos/cli/pkg/kube" "github.com/odigos-io/odigos/common" "sigs.k8s.io/controller-runtime/pkg/client" + certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -18,10 +23,12 @@ import ( ) const ( - InstrumentorServiceName = "instrumentor" - InstrumentorDeploymentName = "odigos-instrumentor" - InstrumentorAppLabelValue = "odigos-instrumentor" - InstrumentorContainerName = "manager" + InstrumentorServiceName = "instrumentor" + InstrumentorDeploymentName = "odigos-instrumentor" + InstrumentorAppLabelValue = "odigos-instrumentor" + InstrumentorContainerName = "manager" + InstrumentorWebhookSecretName = "instrumentor-webhook-cert" + InstrumentorWebhookVolumeName = "webhook-cert" ) func NewInstrumentorServiceAccount(ns string) *corev1.ServiceAccount { @@ -219,6 +226,193 @@ func NewInstrumentorClusterRoleBinding(ns string) *rbacv1.ClusterRoleBinding { } } +func isCertManagerInstalled(ctx context.Context, c *kube.Client) bool { + // Check if CRD is installed + _, err := c.ApiExtensions.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, "issuers.cert-manager.io", metav1.GetOptions{}) + if err != nil { + return false + } + + return true +} + +func NewInstrumentorIssuer(ns string) *certv1.Issuer { + return &certv1.Issuer{ + TypeMeta: metav1.TypeMeta{ + Kind: "Issuer", + APIVersion: "cert-manager.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "selfsigned-issuer", + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "issuer", + "app.kubernetes.io/instance": "selfsigned-issuer", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Spec: certv1.IssuerSpec{ + IssuerConfig: certv1.IssuerConfig{ + SelfSigned: &certv1.SelfSignedIssuer{}, + }, + }, + } +} + +func NewInstrumentorCertificate(ns string) *certv1.Certificate { + return &certv1.Certificate{ + TypeMeta: metav1.TypeMeta{ + Kind: "Certificate", + APIVersion: "cert-manager.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "serving-cert", + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "instrumentor-cert", + "app.kubernetes.io/instance": "instrumentor-cert", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Spec: certv1.CertificateSpec{ + DNSNames: []string{ + fmt.Sprintf("odigos-instrumentor.%s.svc", ns), + fmt.Sprintf("odigos-instrumentor.%s.svc.cluster.local", ns), + }, + IssuerRef: cmmeta.ObjectReference{ + Kind: "Issuer", + Name: "selfsigned-issuer", + }, + SecretName: InstrumentorWebhookSecretName, + }, + } +} + +func NewInstrumentorService(ns string) *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "odigos-instrumentor", + Namespace: ns, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "webhook-server", + Port: 9443, + TargetPort: intstr.FromInt(9443), + }, + }, + Selector: map[string]string{ + "app.kubernetes.io/name": InstrumentorAppLabelValue, + }, + }, + } +} + +func NewMutatingWebhookConfiguration(ns string, caBundle []byte) *admissionregistrationv1.MutatingWebhookConfiguration { + webhook := &admissionregistrationv1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + Kind: "MutatingWebhookConfiguration", + APIVersion: "admissionregistration.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "mutating-webhook-configuration", + Labels: map[string]string{ + "app.kubernetes.io/name": "pod-mutating-webhook", + "app.kubernetes.io/instance": "mutating-webhook-configuration", + "app.kubernetes.io/component": "webhook", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + }, + Webhooks: []admissionregistrationv1.MutatingWebhook{ + { + Name: "pod-mutating-webhook.odigos.io", + ClientConfig: admissionregistrationv1.WebhookClientConfig{ + Service: &admissionregistrationv1.ServiceReference{ + Name: "odigos-instrumentor", + Namespace: ns, + Path: ptrString("/mutate--v1-pod"), + Port: intPtr(9443), + }, + }, + Rules: []admissionregistrationv1.RuleWithOperations{ + { + Operations: []admissionregistrationv1.OperationType{ + admissionregistrationv1.Create, + admissionregistrationv1.Update, + }, + Rule: admissionregistrationv1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + Scope: ptrGeneric(admissionregistrationv1.NamespacedScope), + }, + }, + }, + FailurePolicy: ptrGeneric(admissionregistrationv1.Ignore), + ReinvocationPolicy: ptrGeneric(admissionregistrationv1.IfNeededReinvocationPolicy), + SideEffects: ptrGeneric(admissionregistrationv1.SideEffectClassNone), + TimeoutSeconds: intPtr(10), + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "odigos.io/inject-instrumentation": "true", + }, + }, + AdmissionReviewVersions: []string{ + "v1", + }, + }, + }, + } + + if caBundle == nil { + webhook.Annotations = map[string]string{ + "cert-manager.io/inject-ca-from": fmt.Sprintf("%s/serving-cert", ns), + } + } else { + webhook.Webhooks[0].ClientConfig.CABundle = caBundle + } + + return webhook +} + +func NewInstrumentorTLSSecret(ns string, cert *crypto.Certificate) *corev1.Secret { + return &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: InstrumentorWebhookSecretName, + Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/name": "instrumentor-cert", + "app.kubernetes.io/instance": "instrumentor-cert", + "app.kubernetes.io/component": "certificate", + "app.kubernetes.io/created-by": "instrumentor", + "app.kubernetes.io/part-of": "odigos", + }, + Annotations: map[string]string{ + "helm.sh/hook": "pre-install,pre-upgrade", + "helm.sh/hook-delete-policy": "before-hook-creation", + }, + }, + Data: map[string][]byte{ + "tls.crt": []byte(cert.Cert), + "tls.key": []byte(cert.Key), + }, + } +} + func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, imagePrefix string, imageName string) *appsv1.Deployment { args := []string{ "--health-probe-bind-address=:8081", @@ -230,7 +424,7 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, args = append(args, "--telemetry-disabled") } - return &appsv1.Deployment{ + dep := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", @@ -290,6 +484,20 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, }, }, }, + Ports: []corev1.ContainerPort{ + { + Name: "webhook-server", + ContainerPort: 9443, + Protocol: corev1.ProtocolTCP, + }, + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: InstrumentorWebhookVolumeName, + ReadOnly: true, + MountPath: "/tmp/k8s-webhook-server/serving-certs", + }, + }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ "cpu": resource.MustParse("500m"), @@ -324,12 +532,25 @@ func NewInstrumentorDeployment(ns string, version string, telemetryEnabled bool, SecurityContext: &corev1.PodSecurityContext{ RunAsNonRoot: ptrbool(true), }, + Volumes: []corev1.Volume{ + { + Name: InstrumentorWebhookVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: InstrumentorWebhookSecretName, + DefaultMode: ptrint32(420), + }, + }, + }, + }, }, }, Strategy: appsv1.DeploymentStrategy{}, MinReadySeconds: 0, }, } + + return dep } func ptrint32(i int32) *int32 { @@ -363,12 +584,43 @@ func NewInstrumentorResourceManager(client *kube.Client, ns string, config *comm func (a *instrumentorResourceManager) Name() string { return "Instrumentor" } func (a *instrumentorResourceManager) InstallFromScratch(ctx context.Context) error { + certManagerInstalled := isCertManagerInstalled(ctx, a.client) resources := []client.Object{ NewInstrumentorServiceAccount(a.ns), NewInstrumentorRoleBinding(a.ns), NewInstrumentorClusterRole(), NewInstrumentorClusterRoleBinding(a.ns), NewInstrumentorDeployment(a.ns, a.odigosVersion, a.config.TelemetryEnabled, a.config.ImagePrefix, a.config.InstrumentorImage), + NewInstrumentorService(a.ns), + } + + if certManagerInstalled { + resources = append([]client.Object{NewInstrumentorIssuer(a.ns), + NewInstrumentorCertificate(a.ns), + NewMutatingWebhookConfiguration(a.ns, nil), + }, + resources...) + } else { + ca, err := crypto.GenCA("odigos-instrumentor", 365) + if err != nil { + return fmt.Errorf("failed to generate CA: %w", err) + } + + altNames := []string{ + fmt.Sprintf("odigos-instrumentor.%s.svc", a.ns), + fmt.Sprintf("odigos-instrumentor.%s.svc.cluster.local", a.ns), + } + + cert, err := crypto.GenerateSignedCertificate("serving-cert", nil, altNames, 365, ca) + if err != nil { + return fmt.Errorf("failed to generate signed certificate: %w", err) + } + + resources = append([]client.Object{NewInstrumentorTLSSecret(a.ns, &cert), + NewMutatingWebhookConfiguration(a.ns, []byte(cert.Cert)), + }, + resources...) } + return a.client.ApplyResources(ctx, a.config.ConfigVersion, resources) } diff --git a/cli/cmd/resources/owntelemetry.go b/cli/cmd/resources/owntelemetry.go index 370b9ea96..934bf50d6 100644 --- a/cli/cmd/resources/owntelemetry.go +++ b/cli/cmd/resources/owntelemetry.go @@ -250,6 +250,14 @@ func int64Ptr(n int64) *int64 { return &n } +func ptrString(s string) *string { + return &s +} + +func ptrGeneric[T any](v T) *T { + return &v +} + type ownTelemetryResourceManager struct { client *kube.Client ns string diff --git a/cli/go.mod b/cli/go.mod index 66b1a7691..dab0338ce 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -3,6 +3,7 @@ module github.com/odigos-io/odigos/cli go 1.22.0 require ( + github.com/cert-manager/cert-manager v1.15.3 github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.7.0 github.com/odigos-io/odigos/api v0.0.0 @@ -22,31 +23,31 @@ require ( github.com/fatih/color v1.16.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/goccy/go-yaml v1.11.3 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/spdystream v0.4.0 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + sigs.k8s.io/gateway-api v1.1.0 // indirect ) require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/go-logr/logr v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -63,12 +64,12 @@ require ( golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/cli/go.sum b/cli/go.sum index 24b9458c0..c67c4944a 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -1,13 +1,14 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cert-manager/cert-manager v1.15.3 h1:/u9T0griwd5MegPfWbB7v0KcVcT9OJrEvPNhc9tl7xQ= +github.com/cert-manager/cert-manager v1.15.3/go.mod h1:stBge/DTvrhfQMB/93+Y62s+gQgZBsfL1o0C/4AL/mI= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= +github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -18,13 +19,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= @@ -51,12 +51,12 @@ github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2 github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -65,11 +65,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= @@ -109,12 +106,7 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= @@ -136,8 +128,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -164,8 +156,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -188,10 +180,8 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= @@ -204,12 +194,14 @@ k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f h1:0LQagt0gDpKqvIkAMPaRGcXawNMouPECM1+F9BVxEaM= +k8s.io/kube-openapi v0.0.0-20240430033511-f0e62f92d13f/go.mod h1:S9tOR0FxgyusSNR+MboCuiDpVWkAifZvaYI1Q2ubgro= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= +sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/cli/pkg/crypto/crypto.go b/cli/pkg/crypto/crypto.go new file mode 100644 index 000000000..a60322bf9 --- /dev/null +++ b/cli/pkg/crypto/crypto.go @@ -0,0 +1,326 @@ +package crypto + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "strings" + "time" +) + +// Source: https://github.com/Masterminds/sprig/blob/master/crypto.go +type Certificate struct { + Cert string + Key string +} + +func GenCA( + cn string, + daysValid int, +) (Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return Certificate{}, fmt.Errorf("error generating rsa key: %s", err) + } + + return generateCertificateAuthorityWithKeyInternal(cn, daysValid, priv) +} + +func GenerateSignedCertificate( + cn string, + ips []interface{}, + alternateDNS []string, + daysValid int, + ca Certificate, +) (Certificate, error) { + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return Certificate{}, fmt.Errorf("error generating rsa key: %s", err) + } + return generateSignedCertificateWithKeyInternal(cn, ips, alternateDNS, daysValid, ca, priv) +} + +func generateSignedCertificateWithKeyInternal( + cn string, + ips []interface{}, + alternateDNS []string, + daysValid int, + ca Certificate, + priv crypto.PrivateKey, +) (Certificate, error) { + cert := Certificate{} + + decodedSignerCert, _ := pem.Decode([]byte(ca.Cert)) + if decodedSignerCert == nil { + return cert, errors.New("unable to decode Certificate") + } + signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes) + if err != nil { + return cert, fmt.Errorf( + "error parsing Certificate: decodedSignerCert.Bytes: %s", + err, + ) + } + signerKey, err := parsePrivateKeyPEM(ca.Key) + if err != nil { + return cert, fmt.Errorf( + "error parsing private key: %s", + err, + ) + } + + template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid) + if err != nil { + return cert, err + } + + cert.Cert, cert.Key, err = getCertAndKey( + template, + priv, + signerCert, + signerKey, + ) + + return cert, err +} + +func parsePrivateKeyPEM(pemBlock string) (crypto.PrivateKey, error) { + block, _ := pem.Decode([]byte(pemBlock)) + if block == nil { + return nil, errors.New("no PEM data in input") + } + + if block.Type == "PRIVATE KEY" { + priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("decoding PEM as PKCS#8: %s", err) + } + return priv, nil + } else if !strings.HasSuffix(block.Type, " PRIVATE KEY") { + return nil, fmt.Errorf("no private key data in PEM block of type %s", block.Type) + } + + switch block.Type[:len(block.Type)-12] { // strip " PRIVATE KEY" + case "RSA": + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parsing RSA private key from PEM: %s", err) + } + return priv, nil + case "EC": + priv, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parsing EC private key from PEM: %s", err) + } + return priv, nil + case "DSA": + var k DSAKeyFormat + _, err := asn1.Unmarshal(block.Bytes, &k) + if err != nil { + return nil, fmt.Errorf("parsing DSA private key from PEM: %s", err) + } + priv := &dsa.PrivateKey{ + PublicKey: dsa.PublicKey{ + Parameters: dsa.Parameters{ + P: k.P, Q: k.Q, G: k.G, + }, + Y: k.Y, + }, + X: k.X, + } + return priv, nil + default: + return nil, fmt.Errorf("invalid private key type %s", block.Type) + } +} + +func generateCertificateAuthorityWithKeyInternal( + cn string, + daysValid int, + priv crypto.PrivateKey, +) (Certificate, error) { + ca := Certificate{} + + template, err := getBaseCertTemplate(cn, nil, nil, daysValid) + if err != nil { + return ca, err + } + // Override KeyUsage and IsCA + template.KeyUsage = x509.KeyUsageKeyEncipherment | + x509.KeyUsageDigitalSignature | + x509.KeyUsageCertSign + template.IsCA = true + + ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv) + + return ca, err +} + +func getCertAndKey( + template *x509.Certificate, + signeeKey crypto.PrivateKey, + parent *x509.Certificate, + signingKey crypto.PrivateKey, +) (string, string, error) { + signeePubKey, err := getPublicKey(signeeKey) + if err != nil { + return "", "", fmt.Errorf("error retrieving public key from signee key: %s", err) + } + derBytes, err := x509.CreateCertificate( + rand.Reader, + template, + parent, + signeePubKey, + signingKey, + ) + if err != nil { + return "", "", fmt.Errorf("error creating Certificate: %s", err) + } + + certBuffer := bytes.Buffer{} + if err := pem.Encode( + &certBuffer, + &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}, + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding Certificate: %s", err) + } + + keyBuffer := bytes.Buffer{} + if err := pem.Encode( + &keyBuffer, + pemBlockForKey(signeeKey), + ); err != nil { + return "", "", fmt.Errorf("error pem-encoding key: %s", err) + } + + return certBuffer.String(), keyBuffer.String(), nil +} + +func getBaseCertTemplate( + cn string, + ips []interface{}, + dnsNames []string, + daysValid int, +) (*x509.Certificate, error) { + ipAddresses, err := getNetIPs(ips) + if err != nil { + return nil, err + } + + serialNumberUpperBound := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberUpperBound) + if err != nil { + return nil, err + } + return &x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: cn, + }, + IPAddresses: ipAddresses, + DNSNames: dnsNames, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageServerAuth, + x509.ExtKeyUsageClientAuth, + }, + BasicConstraintsValid: true, + }, nil +} + +func getNetIPs(ips []interface{}) ([]net.IP, error) { + if ips == nil { + return []net.IP{}, nil + } + var ipStr string + var ok bool + var netIP net.IP + netIPs := make([]net.IP, len(ips)) + for i, ip := range ips { + ipStr, ok = ip.(string) + if !ok { + return nil, fmt.Errorf("error parsing ip: %v is not a string", ip) + } + netIP = net.ParseIP(ipStr) + if netIP == nil { + return nil, fmt.Errorf("error parsing ip: %s", ipStr) + } + netIPs[i] = netIP + } + return netIPs, nil +} + +func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) { + if alternateDNS == nil { + return []string{}, nil + } + var dnsStr string + var ok bool + alternateDNSStrs := make([]string, len(alternateDNS)) + for i, dns := range alternateDNS { + dnsStr, ok = dns.(string) + if !ok { + return nil, fmt.Errorf( + "error processing alternate dns name: %v is not a string", + dns, + ) + } + alternateDNSStrs[i] = dnsStr + } + return alternateDNSStrs, nil +} + +func getPublicKey(priv crypto.PrivateKey) (crypto.PublicKey, error) { + switch k := priv.(type) { + case interface{ Public() crypto.PublicKey }: + return k.Public(), nil + case *dsa.PrivateKey: + return &k.PublicKey, nil + default: + return nil, fmt.Errorf("unable to get public key for type %T", priv) + } +} + +// DSAKeyFormat stores the format for DSA keys. +// Used by pemBlockForKey +type DSAKeyFormat struct { + Version int + P, Q, G, Y, X *big.Int +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *dsa.PrivateKey: + val := DSAKeyFormat{ + P: k.P, Q: k.Q, G: k.G, + Y: k.Y, X: k.X, + } + bytes, _ := asn1.Marshal(val) + return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} + case *ecdsa.PrivateKey: + b, _ := x509.MarshalECPrivateKey(k) + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + // attempt PKCS#8 format for all other keys + b, err := x509.MarshalPKCS8PrivateKey(k) + if err != nil { + return nil + } + return &pem.Block{Type: "PRIVATE KEY", Bytes: b} + } +} diff --git a/helm/odigos/templates/_helpers.tpl b/helm/odigos/templates/_helpers.tpl new file mode 100644 index 000000000..f2a0003b9 --- /dev/null +++ b/helm/odigos/templates/_helpers.tpl @@ -0,0 +1,13 @@ +{{- define "utils.certManagerApiVersion" -}} +{{- if .Capabilities.APIVersions.Has "cert-manager.io/v1" -}} +cert-manager.io/v1 +{{- else if .Capabilities.APIVersions.Has "cert-manager.io/v1beta1" -}} +cert-manager.io/v1beta1 +{{- else if .Capabilities.APIVersions.Has "cert-manager.io/v1alpha2" -}} +cert-manager.io/v1alpha2 +{{- else if .Capabilities.APIVersions.Has "certmanager.k8s.io/v1alpha1" -}} +certmanager.k8s.io/v1alpha1 +{{- else -}} +{{- print "" -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/certificates.yaml b/helm/odigos/templates/instrumentor/certificates.yaml new file mode 100644 index 000000000..ab55f6c51 --- /dev/null +++ b/helm/odigos/templates/instrumentor/certificates.yaml @@ -0,0 +1,36 @@ +{{- $certManagerApiVersion := include "utils.certManagerApiVersion" . -}} +{{- if $certManagerApiVersion }} +apiVersion: {{ $certManagerApiVersion }} +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: issuer + app.kubernetes.io/instance: selfsigned-issuer + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +spec: + selfSigned: {} +--- +apiVersion: {{ $certManagerApiVersion }} +kind: Certificate +metadata: + name: serving-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: instrumentor-cert + app.kubernetes.io/instance: instrumentor-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +spec: + dnsNames: + - "odigos-instrumentor.{{ .Release.Namespace }}.svc" + - "odigos-instrumentor.{{ .Release.Namespace }}.svc.cluster.local" + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: instrumentor-webhook-cert +{{- end }} \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/deployment.yaml b/helm/odigos/templates/instrumentor/deployment.yaml index a5a3e58ac..6093737de 100644 --- a/helm/odigos/templates/instrumentor/deployment.yaml +++ b/helm/odigos/templates/instrumentor/deployment.yaml @@ -29,6 +29,10 @@ spec: {{- else }} image: "{{ .Values.instrumentor.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" {{- end }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP env: - name: OTEL_SERVICE_NAME value: instrumentor @@ -36,6 +40,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + volumeMounts: + - name: webhook-cert + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true envFrom: - configMapRef: name: odigos-own-telemetry-otel-config @@ -59,6 +67,11 @@ spec: securityContext: runAsNonRoot: true serviceAccountName: odigos-instrumentor + volumes: + - name: webhook-cert + secret: + secretName: instrumentor-webhook-cert + defaultMode: 420 terminationGracePeriodSeconds: 10 {{- if .Values.imagePullSecrets }} imagePullSecrets: diff --git a/helm/odigos/templates/instrumentor/service.yaml b/helm/odigos/templates/instrumentor/service.yaml new file mode 100644 index 000000000..7d1857813 --- /dev/null +++ b/helm/odigos/templates/instrumentor/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: odigos-instrumentor + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: webhook-server + port: 9443 + targetPort: 9443 + selector: + app.kubernetes.io/name: odigos-instrumentor \ No newline at end of file diff --git a/helm/odigos/templates/instrumentor/webhook.yaml b/helm/odigos/templates/instrumentor/webhook.yaml new file mode 100644 index 000000000..41ed17f26 --- /dev/null +++ b/helm/odigos/templates/instrumentor/webhook.yaml @@ -0,0 +1,66 @@ +{{- $certManagerApiVersion := include "utils.certManagerApiVersion" . -}} +{{- $altNames := list (printf "odigos-instrumentor.%s.svc" .Release.Namespace) (printf "odigos-instrumentor.%s.svc.cluster.local" .Release.Namespace) -}} +{{- $ca := genCA "serving-cert" 365 -}} +{{- $cert := genSignedCert "serving-cert" nil $altNames 365 $ca -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + labels: + app.kubernetes.io/name: pod-mutating-webhook + app.kubernetes.io/instance: mutating-webhook-configuration + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos +{{- if $certManagerApiVersion }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/serving-cert +{{- end }} +webhooks: + - name: pod-mutating-webhook.odigos.io + clientConfig: +{{- if not $certManagerApiVersion }} + caBundle: {{ $ca.Cert | b64enc }} +{{- end }} + service: + name: odigos-instrumentor + namespace: {{ .Release.Namespace }} + path: /mutate--v1-pod + port: 9443 + rules: + - operations: + - CREATE + - UPDATE + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + scope: Namespaced + failurePolicy: Ignore + reinvocationPolicy: IfNeeded + sideEffects: None + objectSelector: + matchLabels: + odigos.io/inject-instrumentation: "true" + timeoutSeconds: 10 + admissionReviewVersions: ["v1"] +--- +{{- if not $certManagerApiVersion }} +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: instrumentor-webhook-cert + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: instrumentor-cert + app.kubernetes.io/instance: instrumentor-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: instrumentor + app.kubernetes.io/part-of: odigos + annotations: + "helm.sh/hook": "pre-install,pre-upgrade" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: + tls.crt: {{ $cert.Cert | b64enc }} + tls.key: {{ $cert.Key | b64enc }} +{{- end }} \ No newline at end of file diff --git a/instrumentor/PROJECT b/instrumentor/PROJECT index 83a4eb6c1..46f3dd370 100644 --- a/instrumentor/PROJECT +++ b/instrumentor/PROJECT @@ -44,4 +44,11 @@ resources: kind: DaemonSet path: k8s.io/api/apps/v1 version: v1 +- domain: odigos.io + kind: Pod + path: k8s.io/api/core/v1 + version: v1 + webhooks: + defaulting: true + webhookVersion: v1 version: "3" diff --git a/instrumentor/controllers/instrumentationdevice/manager.go b/instrumentor/controllers/instrumentationdevice/manager.go index 8fb5d8b68..8b37b0015 100644 --- a/instrumentor/controllers/instrumentationdevice/manager.go +++ b/instrumentor/controllers/instrumentationdevice/manager.go @@ -165,5 +165,14 @@ func SetupWithManager(mgr ctrl.Manager) error { return err } + err = builder. + WebhookManagedBy(mgr). + For(&corev1.Pod{}). + WithDefaulter(&PodsWebhook{}). + Complete() + if err != nil { + return err + } + return nil } diff --git a/instrumentor/controllers/instrumentationdevice/pods_webhook.go b/instrumentor/controllers/instrumentationdevice/pods_webhook.go new file mode 100644 index 000000000..45fb0594c --- /dev/null +++ b/instrumentor/controllers/instrumentationdevice/pods_webhook.go @@ -0,0 +1,34 @@ +package instrumentationdevice + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/webhook" + + corev1 "k8s.io/api/core/v1" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "k8s.io/apimachinery/pkg/runtime" +) + +type PodsWebhook struct{} + +var _ webhook.CustomDefaulter = &PodsWebhook{} + +func (p *PodsWebhook) Default(ctx context.Context, obj runtime.Object) error { + // TODO(edenfed): add object selector to mutatingwebhookconfiguration + log := logf.FromContext(ctx) + pod, ok := obj.(*corev1.Pod) + if !ok { + return fmt.Errorf("expected a Pod but got a %T", obj) + } + + if pod.Annotations == nil { + pod.Annotations = map[string]string{} + } + + //pod.Annotations["odigos.io/instrumented-webhook"] = "true" + log.V(0).Info("Defaulted Pod", "name", pod.Name) + return nil +} diff --git a/instrumentor/controllers/instrumentationdevice/suite_test.go b/instrumentor/controllers/instrumentationdevice/suite_test.go index 4b7681edc..c8caedc57 100644 --- a/instrumentor/controllers/instrumentationdevice/suite_test.go +++ b/instrumentor/controllers/instrumentationdevice/suite_test.go @@ -21,6 +21,8 @@ import ( "path/filepath" "testing" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/odigos-io/odigos/instrumentor/internal/testutil" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -93,8 +95,14 @@ var _ = BeforeSuite(func() { odigosConfiguration := testutil.NewMockOdigosConfig() Expect(k8sClient.Create(testCtx, odigosConfiguration)).Should(Succeed()) + webhookInstallOptions := &testEnv.WebhookInstallOptions k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme.Scheme, + WebhookServer: webhook.NewServer(webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), }) Expect(err).ToNot(HaveOccurred()) From 80d646b34cd4b925b7be294468c2ed7c9440a2c8 Mon Sep 17 00:00:00 2001 From: yodigos Date: Tue, 1 Oct 2024 10:16:23 +0300 Subject: [PATCH 3/5] fix: Fix Workload Tests (#1561) --- tests/common/traceql_runner.sh | 2 +- .../workload-lifecycle/02-wait-for-trace.yaml | 2 +- .../e2e/workload-lifecycle/chainsaw-test.yaml | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/common/traceql_runner.sh b/tests/common/traceql_runner.sh index b36bd8e3c..a77d02f6d 100755 --- a/tests/common/traceql_runner.sh +++ b/tests/common/traceql_runner.sh @@ -42,7 +42,7 @@ function process_yaml_file() { one_hour=3600 start_epoch=$(($current_epoch - one_hour)) end_epoch=$(($current_epoch + one_hour)) - response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/api/search\?end=$end_epoch\&start=$start_epoch\&q=$encoded_query) + response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/api/search\?end=$end_epoch\&start=$start_epoch\&q=$encoded_query\&limit=50) num_of_traces=$(echo $response | jq '.traces | length') # if num_of_traces not equal to expected_count if [ "$num_of_traces" -ne "$expected_count" ]; then diff --git a/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml b/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml index 00d4dc1c7..a984e8109 100644 --- a/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml +++ b/tests/e2e/workload-lifecycle/02-wait-for-trace.yaml @@ -6,8 +6,8 @@ query: | { resource.service.name = "nodejs-latest-version" } || { resource.service.name = "nodejs-dockerfile-env" } || { resource.service.name = "nodejs-manifest-env" } || + { resource.service.name = "language-change" } || { resource.service.name = "java-supported-version" } || - { resource.service.name = "language-change" } || { resource.service.name = "java-latest-version" } || { resource.service.name = "java-old-version" } || { resource.service.name = "java-supported-docker-env" } || diff --git a/tests/e2e/workload-lifecycle/chainsaw-test.yaml b/tests/e2e/workload-lifecycle/chainsaw-test.yaml index c9c112b2b..e2f229572 100644 --- a/tests/e2e/workload-lifecycle/chainsaw-test.yaml +++ b/tests/e2e/workload-lifecycle/chainsaw-test.yaml @@ -43,9 +43,14 @@ spec: - script: timeout: 60s content: | - helm repo add grafana https://grafana.github.io/helm-charts - helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm repo add grafana https://grafana.github.io/helm-charts + helm repo update + helm install e2e-tests grafana/tempo \ + -n traces --create-namespace \ + --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready @@ -122,12 +127,15 @@ spec: # Delete the job kubectl delete -f 01-generate-traffic.yaml + + - name: '01 - Wait for Traces' try: - script: timeout: 60s content: | + sleep 20 while true; do ../../common/traceql_runner.sh 01-wait-for-trace.yaml if [ $? -eq 0 ]; then @@ -177,12 +185,14 @@ spec: job_name=$(kubectl get -f 01-generate-traffic.yaml -o=jsonpath='{.metadata.name}') kubectl wait --for=condition=complete job/$job_name kubectl delete -f 01-generate-traffic.yaml - + - name: '02 - Wait for Traces' try: - script: timeout: 60s content: | + sleep 20 + while true; do ../../common/traceql_runner.sh 02-wait-for-trace.yaml if [ $? -eq 0 ]; then From 61a0b3b2126f9e702eb8a639fc0676db18683943 Mon Sep 17 00:00:00 2001 From: Alon Braymok <138359965+alonkeyval@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:20:30 +0300 Subject: [PATCH 4/5] [GEN-1462] feat: fast sources selection (#1563) added tree checkboxs selection for large scale clusters --- .../sources.option.menu.tsx | 8 + .../setup/sources/fast-sources-selection.tsx | 216 ++++++++++++++++++ .../setup/sources/namespace-accordion.tsx | 125 ++++++++++ .../setup/sources/sources.section.tsx | 17 +- .../webapp/design.system/drawer/index.tsx | 76 ++++++ frontend/webapp/design.system/index.tsx | 1 + 6 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 frontend/webapp/containers/setup/sources/fast-sources-selection.tsx create mode 100644 frontend/webapp/containers/setup/sources/namespace-accordion.tsx create mode 100644 frontend/webapp/design.system/drawer/index.tsx diff --git a/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx b/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx index 75f5cdf26..e26065162 100644 --- a/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx +++ b/frontend/webapp/components/setup/sources/sources.option.menu/sources.option.menu.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { SourcesOptionMenuWrapper } from './sources.option.menu.styled'; import { FilterSourcesOptions } from './filter.sources.options'; import { ActionSourcesOptions } from './action.sources.options'; +import { KeyvalButton, KeyvalText } from '@/design.system'; +import theme from '@/styles/palette'; export function SourcesOptionMenu({ setCurrentItem, @@ -12,6 +14,7 @@ export function SourcesOptionMenu({ selectedApplications, currentNamespace, onFutureApplyChange, + toggleFastSourcesSelection, }: any) { return ( @@ -28,6 +31,11 @@ export function SourcesOptionMenu({ selectedApplications={selectedApplications} onFutureApplyChange={onFutureApplyChange} /> + + + Fast Sources Selection + + ); } diff --git a/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx b/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx new file mode 100644 index 000000000..9f4edd3ac --- /dev/null +++ b/frontend/webapp/containers/setup/sources/fast-sources-selection.tsx @@ -0,0 +1,216 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useQuery } from 'react-query'; +import { KeyvalButton, KeyvalLoader, KeyvalText } from '@/design.system'; +import { OVERVIEW, QUERIES, ROUTES } from '@/utils'; +import { getApplication, getNamespaces } from '@/services'; +import { + LoaderWrapper, + SectionContainerWrapper, +} from './sources.section.styled'; +import { NamespaceAccordion } from './namespace-accordion'; +import styled from 'styled-components'; +import theme from '@/styles/palette'; +import { useSources } from '@/hooks'; +import { useRouter } from 'next/navigation'; +import { setSources } from '@/store'; +import { useDispatch } from 'react-redux'; + +const TitleWrapper = styled.div` + margin-bottom: 24px; +`; + +const ButtonWrapper = styled.div` + position: absolute; + bottom: 24px; + width: 90%; +`; + +interface AccordionItem { + name: string; + kind: string; + instances: number; + app_instrumentation_labeled: boolean; + ns_instrumentation_labeled: boolean; + instrumentation_effective: boolean; + selected: boolean; +} + +interface AccordionData { + title: string; + items: AccordionItem[]; +} + +export function FastSourcesSelection({ sectionData, setSectionData }) { + const [accordionData, setAccordionData] = useState([]); + const router = useRouter(); + const dispatch = useDispatch(); + const { isLoading, data: namespaces } = useQuery( + [QUERIES.API_NAMESPACES], + getNamespaces + ); + + const { upsertSources } = useSources(); + + useEffect(() => { + if (namespaces) { + const accordionData = namespaces.namespaces.map((item, index) => ({ + title: item.name, + items: + sectionData?.[item.name]?.objects.map((app) => ({ + ...app, + selected: app.selected, + })) || [], + })); + setAccordionData(accordionData); + } + }, [namespaces]); + + const handleSetCurrentNamespace = async (selectedNs) => { + const currentNsApps = await getApplication(selectedNs.title); + + const updatedSectionData = { + ...sectionData, + [selectedNs.title]: { + selected_all: sectionData[selectedNs.title]?.selected_all || false, + future_selected: + sectionData[selectedNs.title]?.future_selected || false, + objects: currentNsApps.applications.map((app) => ({ + ...app, + selected: sectionData[selectedNs.title]?.objects?.find( + (a) => a.name === app.name + )?.selected, + })), + }, + }; + + const accordionData = namespaces.namespaces.map((item) => ({ + title: item.name, + items: updatedSectionData[item.name]?.objects + .map((app) => ({ + ...app, + selected: app.selected, + })) + .filter((app) => !app.instrumentation_effective), + })); + + // Update both sectionData and accordionData + setSectionData(updatedSectionData); + setAccordionData(accordionData); + }; + + const onSelectItemChange = (item: AccordionItem, ns: string) => { + const updatedAccordionData = accordionData.map((a_data: AccordionData) => { + if (a_data.title === ns) { + return { + ...a_data, + items: a_data.items.map((i: AccordionItem) => { + if (i.name === item.name) { + return { ...i, selected: !i.selected }; + } + return i; + }), + }; + } + return a_data; + }); + + const updatedSectionData = { + ...sectionData, + [ns]: { + ...sectionData[ns], + selected_all: accordionData[ns] + ?.find((a) => a.title === ns) + .items.every((i) => i.selected), + future_selected: accordionData[ns] + ?.find((a) => a.title === ns) + .items.some((i) => !i.selected), + objects: sectionData[ns].objects.map((a) => { + if (a.name === item.name) { + return { ...a, selected: !a.selected }; + } + return a; + }), + }, + }; + + setSectionData(updatedSectionData); + + // Update the accordion data state with the modified data + setAccordionData(updatedAccordionData); + }; + + const onSelectAllChange = (ns, value) => { + const updatedAccordionData = accordionData.map((a_data: AccordionData) => { + if (a_data.title === ns) { + return { + ...a_data, + items: a_data.items.map((i: AccordionItem) => { + return { ...i, selected: value }; + }), + }; + } + return a_data; + }); + + const updatedSectionData = { + ...sectionData, + [ns]: { + ...sectionData[ns], + objects: sectionData[ns]?.objects?.map((a) => { + return { ...a, selected: value }; + }), + }, + }; + + // Update the accordion data state with the modified data + setSectionData(updatedSectionData); + setAccordionData(updatedAccordionData); + }; + + function onConnectClick() { + const isSetup = window.location.pathname.includes('choose-sources'); + + if (isSetup) { + dispatch(setSources(sectionData)); + router.push(ROUTES.CHOOSE_DESTINATION); + return; + } + + upsertSources({ + sectionData, + onSuccess: () => router.push(`${ROUTES.SOURCES}?poll=true`), + onError: null, + }); + } + + if (isLoading) { + return ( + + + + ); + } + const isSetup = window.location.pathname.includes('choose-sources'); + return ( + + + Fast Sources Selection + +
+ handleSetCurrentNamespace(data)} + onSelectAllChange={onSelectAllChange} + /> +
+ + + + {isSetup ? 'Next' : OVERVIEW.CONNECT} + + + +
+ ); +} diff --git a/frontend/webapp/containers/setup/sources/namespace-accordion.tsx b/frontend/webapp/containers/setup/sources/namespace-accordion.tsx new file mode 100644 index 000000000..989e0e30e --- /dev/null +++ b/frontend/webapp/containers/setup/sources/namespace-accordion.tsx @@ -0,0 +1,125 @@ +import { KeyvalCheckbox, KeyvalText } from '@/design.system'; +import React, { useEffect, useState } from 'react'; +import { WhiteArrowIcon } from '@keyval-dev/design-system'; +import styled from 'styled-components'; +interface AccordionItemData { + title: string; + items: any[]; +} + +interface AccordionProps { + data: AccordionItemData[]; + setCurrentNamespace: (data: AccordionItemData) => void; + onSelectItem: (item: any, ns: string) => void; + onSelectAllChange: (ns, value) => void; +} + +const ArrowIconWrapper = styled.div<{ expanded: boolean }>` + margin-left: 24px; + transform: rotate(${({ expanded }) => (expanded ? '90deg' : '-90deg')}); +`; + +export function NamespaceAccordion({ + data, + setCurrentNamespace, + onSelectItem, + onSelectAllChange, +}: AccordionProps) { + return ( +
+ {data.map((itemData, index) => ( + + ))} +
+ ); +} + +interface AccordionItemProps { + data: any; + setCurrentNamespace: (data: AccordionItemData) => void; + onSelectItem: (item: any, ns: string) => void; + onSelectAllChange: (ns, value) => void; +} + +function AccordionItem({ + data, + setCurrentNamespace, + onSelectItem, + onSelectAllChange, +}: AccordionItemProps) { + const [isAllSelected, setIsAllSelected] = useState(false); + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + const selectedItems = data.items?.filter((item) => item.selected); + setIsAllSelected( + selectedItems?.length === data?.items?.length && selectedItems?.length > 0 + ); + }, [data]); + + const handleSelectAllChange = (ns, value) => { + onSelectAllChange(ns, value); + setIsAllSelected(value); + }; + + const handleItemChange = (item) => { + onSelectItem(item, data.title); + }; + + const handleExpand = () => { + setCurrentNamespace(data); + setExpanded(!expanded); + }; + + return ( +
+
+ handleSelectAllChange(data.title, !isAllSelected)} + label={''} + /> +
+ + {data.title} + + + + +
+
+ {expanded && ( +
+ {data.items?.map((item, index) => ( +
+ handleItemChange(item)} + label={`${item.name} / ${item.kind.toLowerCase()}`} + /> +
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/webapp/containers/setup/sources/sources.section.tsx b/frontend/webapp/containers/setup/sources/sources.section.tsx index 525bb821a..4df611d0f 100644 --- a/frontend/webapp/containers/setup/sources/sources.section.tsx +++ b/frontend/webapp/containers/setup/sources/sources.section.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; -import { KeyvalLoader } from '@/design.system'; +import { Drawer, KeyvalLoader } from '@/design.system'; import { NOTIFICATION, QUERIES } from '@/utils'; import { getApplication, getNamespaces } from '@/services'; import { SourcesList, SourcesOptionMenu } from '@/components/setup'; @@ -14,12 +14,14 @@ import { NamespaceConfiguration, SelectedSourcesConfiguration, } from '@/types'; +import { FastSourcesSelection } from './fast-sources-selection'; const DEFAULT = 'default'; export function SourcesSection({ sectionData, setSectionData }) { const [currentNamespace, setCurrentNamespace] = useState(); const [searchFilter, setSearchFilter] = useState(''); + const [isDrawerOpen, setDrawerOpen] = useState(false); const { isLoading, data, isError, error } = useQuery( [QUERIES.API_NAMESPACES], @@ -65,6 +67,8 @@ export function SourcesSection({ sectionData, setSectionData }) { ); }, [searchFilter, currentNamespace, sectionData]); + const toggleDrawer = () => setDrawerOpen(!isDrawerOpen); + async function onNameSpaceChange() { if (!currentNamespace || sectionData[currentNamespace?.name]) return; const namespace = await getApplication(currentNamespace?.name); @@ -171,6 +175,16 @@ export function SourcesSection({ sectionData, setSectionData }) { return ( + {isDrawerOpen && ( + + + + )} void; + position?: 'left' | 'right'; // Optional prop to specify the drawer opening side + width?: string; // Optional width control, defaults to 300px + children: React.ReactNode; +} + +// Styled-component for overlay +const Overlay = styled.div<{ isOpen: boolean }>` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; + transition: opacity 0.3s ease; + visibility: ${({ isOpen }) => (isOpen ? 'visible' : 'hidden')}; + z-index: 999; +`; + +// Styled-component for drawer container +const DrawerContainer = styled.div<{ + isOpen: boolean; + position: 'left' | 'right'; + width: string; +}>` + position: fixed; + top: 0; + bottom: 0; + ${({ position, width }) => position}: 0; + width: ${({ width }) => width}; + background-color: ${({ theme }) => theme.colors.light_dark}; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + transform: translateX( + ${({ isOpen, position }) => + isOpen ? '0' : position === 'left' ? '-100%' : '100%'} + ); + transition: transform 0.3s ease; + z-index: 1000; + overflow-y: auto; +`; + +export const Drawer: React.FC = ({ + isOpen, + onClose, + position = 'right', + width = '300px', + children, +}) => { + // Handle closing the drawer when escape key is pressed + useEffect(() => { + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen) { + onClose(); + } + }; + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('keydown', handleEscape); + }; + }, [isOpen, onClose]); + + return ( + <> + + + {children} + + + ); +}; diff --git a/frontend/webapp/design.system/index.tsx b/frontend/webapp/design.system/index.tsx index d11dd61c2..2a95ab080 100644 --- a/frontend/webapp/design.system/index.tsx +++ b/frontend/webapp/design.system/index.tsx @@ -34,3 +34,4 @@ export { OdigosTable as Table } from './table'; export { YMLEditor } from './yml.editor'; export { CodeBlock } from './code-block'; export { Conditions } from './condition.alert'; +export { Drawer } from './drawer'; From 892b759e500281b59c5e89ddf1a15625b351bb25 Mon Sep 17 00:00:00 2001 From: yodigos Date: Tue, 1 Oct 2024 11:29:10 +0300 Subject: [PATCH 5/5] feat: Describe doc and add tempo configuration to all the other e2e tests (#1562) --- docs/cli/odigos_diagnose.mdx | 30 +++++++++++++++++++++++ docs/mint.json | 1 + tests/e2e/cli-upgrade/chainsaw-test.yaml | 5 +++- tests/e2e/fe-synthetic/chainsaw-test.yaml | 5 +++- tests/e2e/helm-chart/chainsaw-test.yaml | 5 +++- tests/e2e/multi-apps/chainsaw-test.yaml | 5 +++- 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 docs/cli/odigos_diagnose.mdx diff --git a/docs/cli/odigos_diagnose.mdx b/docs/cli/odigos_diagnose.mdx new file mode 100644 index 000000000..5691d612e --- /dev/null +++ b/docs/cli/odigos_diagnose.mdx @@ -0,0 +1,30 @@ +--- +title: "odigos diagnose" +sidebarTitle: "odigos diagnose" +--- + +Retrieves Logs of all Odigos Components in the odigos-system namespace and CRDs of Actions, Instrumentation resources and more.
+The results will be saved in a compressed file for further troubleshooting. + +## Synopsis + +This command can be used for troubleshooting and observing Odigos state. +The file will be saved in this format: odigos_debug_ddmmyyyyhhmmss.tar.gz + + +```bash +odigos diagnose +``` + +## Output + +``` +odigos_debug_25092024102322.tar.gz +``` + + +## SEE ALSO + +* [odigos](/cli/odigos) - odigos CLI +* [odigos describe](/cli/odigos_describe) - Show details of a specific odigos entity + diff --git a/docs/mint.json b/docs/mint.json index 8f62c20c3..e76afb30d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -80,6 +80,7 @@ "cli/odigos", "cli/odigos_cloud", "cli/odigos_describe", + "cli/odigos_diagnose", "cli/odigos_install", "cli/odigos_profile", "cli/odigos_ui", diff --git a/tests/e2e/cli-upgrade/chainsaw-test.yaml b/tests/e2e/cli-upgrade/chainsaw-test.yaml index 9a07dbf3f..481afb7ee 100644 --- a/tests/e2e/cli-upgrade/chainsaw-test.yaml +++ b/tests/e2e/cli-upgrade/chainsaw-test.yaml @@ -16,7 +16,10 @@ spec: else helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 fi - assert: file: assert-tempo-running.yaml diff --git a/tests/e2e/fe-synthetic/chainsaw-test.yaml b/tests/e2e/fe-synthetic/chainsaw-test.yaml index 8c8df6d1f..db9280410 100644 --- a/tests/e2e/fe-synthetic/chainsaw-test.yaml +++ b/tests/e2e/fe-synthetic/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml diff --git a/tests/e2e/helm-chart/chainsaw-test.yaml b/tests/e2e/helm-chart/chainsaw-test.yaml index 81790c4a9..637bedb81 100644 --- a/tests/e2e/helm-chart/chainsaw-test.yaml +++ b/tests/e2e/helm-chart/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready diff --git a/tests/e2e/multi-apps/chainsaw-test.yaml b/tests/e2e/multi-apps/chainsaw-test.yaml index 4b04f38eb..8dfeb4d47 100644 --- a/tests/e2e/multi-apps/chainsaw-test.yaml +++ b/tests/e2e/multi-apps/chainsaw-test.yaml @@ -13,7 +13,10 @@ spec: content: | helm repo add grafana https://grafana.github.io/helm-charts helm repo update - helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 \ + --set tempo.ingester.trace_idle_period=5s \ + --set tempo.ingester.max_block_duration=20s \ + --version 1.10.1 - assert: file: assert-tempo-running.yaml - name: Wait for destination to be ready