From 1be9213c38058c816623c944547bed7d6274725b Mon Sep 17 00:00:00 2001 From: as673366 Date: Mon, 22 Dec 2025 21:49:20 +0530 Subject: [PATCH 01/11] adding changes to support dual gateway requirements in otk --- api/v1/gateway_types.go | 17 + .../bases/security.brcmlabs.com_gateways.yaml | 32 +- example/gateway/otk/otk-ssg-dmz.yaml | 204 +++++ example/gateway/otk/otk-ssg-internal.yaml | 201 +++++ internal/controller/gateway/controller.go | 5 + pkg/api/reconcile/l7api.go | 4 +- pkg/gateway/reconcile/cron.go | 39 +- pkg/gateway/reconcile/externalkeys.go | 765 ++++++++++++++++++ pkg/gateway/reconcile/gateway.go | 13 +- pkg/gateway/reconcile/l7otkcertificates.go | 117 --- 10 files changed, 1262 insertions(+), 135 deletions(-) create mode 100644 example/gateway/otk/otk-ssg-dmz.yaml create mode 100644 example/gateway/otk/otk-ssg-internal.yaml diff --git a/api/v1/gateway_types.go b/api/v1/gateway_types.go index 41fcad93..646d9929 100644 --- a/api/v1/gateway_types.go +++ b/api/v1/gateway_types.go @@ -360,17 +360,32 @@ type Otk struct { // This configures a relationship between DMZ and Internal Gateways. InternalOtkGatewayReference string `json:"internalGatewayReference,omitempty"` // InternalGatewayPort defaults to 9443 or graphmanDynamicSync port + // This port is used when the Internal gateway is external (not managed by operator) InternalGatewayPort int `json:"internalGatewayPort,omitempty"` // OTKPort is used in Single mode - sets the otk.port cluster-wide property and in Dual-Mode // sets host_oauth2_auth_server port in #OTK Client Context Variables // TODO: Make this an array for many dmz deployments to one internal DmzOtkGatewayReference string `json:"dmzGatewayReference,omitempty"` + // DmzGatewayPort defaults to 9443 or graphmanDynamicSync port + // This port is used when the DMZ gateway is external (not managed by operator) + DmzGatewayPort int `json:"dmzGatewayPort,omitempty"` // OTKPort defaults to 8443 OTKPort int `json:"port,omitempty"` // MaintenanceTasks for the OTK database are disabled by default MaintenanceTasks OtkMaintenanceTasks `json:"maintenanceTasks,omitempty"` // RuntimeSyncIntervalSeconds how often OTK Gateways should be updated in internal/dmz mode RuntimeSyncIntervalSeconds int `json:"runtimeSyncIntervalSeconds,omitempty"` + // SyncIntervalSeconds determines how often DMZ and Internal gateways should update certificates + // Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + SyncIntervalSeconds int `json:"syncIntervalSeconds,omitempty"` + // DmzKeySecret is a reference to a kubernetes.io/tls Secret containing the DMZ private key and certificate + DmzKeySecret string `json:"dmzKeySecret,omitempty"` + // InternalKeySecret is a reference to a kubernetes.io/tls Secret containing the Internal private key and certificate + InternalKeySecret string `json:"internalKeySecret,omitempty"` + // DmzAuthSecret is a reference to a Secret containing username and password for DMZ authentication + DmzAuthSecret string `json:"dmzAuthSecret,omitempty"` + // InternalAuthSecret is a reference to a Secret containing username and password for Internal authentication + InternalAuthSecret string `json:"internalAuthSecret,omitempty"` } // OtkMaintenanceTasks are included in the install bundle as disabled scheduled tasks @@ -893,6 +908,8 @@ type ExternalKey struct { // only one key usage type is allowed // SSL | CA | AUDIT_SIGNING | AUDIT_VIEWER KeyUsageType KeyUsageType `json:"keyUsageType,omitempty"` + // Otk indicates that this key usage was specific for OTK + Otk bool `json:"otk,omitempty"` } type KeyUsageType string diff --git a/config/crd/bases/security.brcmlabs.com_gateways.yaml b/config/crd/bases/security.brcmlabs.com_gateways.yaml index a473a499..0ebb6746 100644 --- a/config/crd/bases/security.brcmlabs.com_gateways.yaml +++ b/config/crd/bases/security.brcmlabs.com_gateways.yaml @@ -1589,6 +1589,10 @@ spec: description: Name of the kubernetes.io/tls Secret which already exists in Kubernetes type: string + otk: + description: Otk indicates that this key usage was specific + for OTK + type: boolean type: object type: array externalSecrets: @@ -4003,9 +4007,21 @@ spec: description: Type of OTK Database type: string type: object + dmzAuthSecret: + description: DmzAuthSecret is a reference to a Secret containing + username and password... + type: string + dmzGatewayPort: + description: |- + DmzGatewayPort defaults to 9443 or graphmanDynamicSync port + This port is... + type: integer dmzGatewayReference: description: OTKPort is used in Single mode - sets the otk. type: string + dmzKeySecret: + description: DmzKeySecret is a reference to a kubernetes. + type: string enabled: description: Enable or disable the OTK initContainer type: boolean @@ -4142,14 +4158,22 @@ spec: type: string type: object type: object + internalAuthSecret: + description: InternalAuthSecret is a reference to a Secret + containing username and... + type: string internalGatewayPort: - description: InternalGatewayPort defaults to 9443 or graphmanDynamicSync - port + description: |- + InternalGatewayPort defaults to 9443 or graphmanDynamicSync port + This port... type: integer internalGatewayReference: description: InternalOtkGatewayReference to an Operator managed Gateway deployment that... type: string + internalKeySecret: + description: InternalKeySecret is a reference to a kubernetes. + type: string maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled by default @@ -4202,6 +4226,10 @@ spec: items: type: string type: array + syncIntervalSeconds: + description: SyncIntervalSeconds determines how often DMZ + and Internal gateways should... + type: integer type: description: Type of OTK installation single, internal or dmz diff --git a/example/gateway/otk/otk-ssg-dmz.yaml b/example/gateway/otk/otk-ssg-dmz.yaml new file mode 100644 index 00000000..9bae180e --- /dev/null +++ b/example/gateway/otk/otk-ssg-dmz.yaml @@ -0,0 +1,204 @@ +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-dmz +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + updateStrategy: + type: rollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 2 + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + externalSecrets: + - name: gateway-secret + enabled: true + variableReferencable: true + description: Gateway Secret + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-dmz-tls-secret + enabled: true + alias: otk-dmz-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: dmz + internalAuthSecret: otk-internal-auth-secret + internalGatewayReference: as673366-gw-upgrade-0.apim.labs.broadcom.net + # InternalGatewayPort is used when the Internal gateway is external (not managed by operator) + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + internalGatewayPort: 8443 + # SyncIntervalSeconds determines how often DMZ and Internal gateways should update certificates + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for DMZ key (used by OTK reconciliation) + dmzKeySecret: otk-dmz-tls-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + # A single secret containing all of the values defined here will be created + # if existingSecret is set the corresponding gateway, readOnly or admin will be omitted from the secret + # if no values are set, a secret will not be created or referenced and the deployment will be invalidated. + # existingSecret: otk-db-secret + gateway: + username: otk_user + password: otkUserPass + readOnly: + # username: readonly_user + username: readonly_user + password: readonly_userPass + admin: + # username: admin + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + #jdbcUrl: jdbc:mysql://:/ + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + autoscaling: + enabled: false + bundle: [] + repositoryReferences: [] + bootstrap: + script: + enabled: true + initContainers: [] + hazelcast: + external: false + endpoint: hazelcast.example.com:5701 + management: + secretName: gateway-secret + #username: admin + #password: 7layer + # Management port requires a separate service... + service: + enabled: true + #annotations: + # cloud.google.com/load-balancer-type: "Internal" + type: LoadBalancer + ports: + - name: management + port: 9443 + targetPort: 9443 + protocol: TCP + restman: + enabled: false + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + dynamicSyncPort: 9443 + cluster: + #password: 7layer + hostname: gateway.brcmlabs.com + database: + enabled: false # this runs the gateway in dbbacked/ephemeral mode + # jdbcUrl: "jdbc:mysql://cluster1-haproxy.pxc.svc.cluster.local:3306/ssg" + # username: "gateway" + # password: "ACm8BDr3Rfk2Flx9V" + java: + jvmHeap: + calculate: true + percentage: 50 + default: 4g + extraArgs: + - -Dcom.l7tech.server.audit.message.saveToInternal=false + - -Dcom.l7tech.server.audit.admin.saveToInternal=false + - -Dcom.l7tech.server.audit.system.saveToInternal=false + - -Dcom.l7tech.server.audit.log.format=json + - -Djava.util.logging.config.file=/opt/SecureSpan/Gateway/node/default/etc/conf/log-override.properties + - -Dcom.l7tech.security.ssl.hostAllowWildcard=true + - -Dcom.l7tech.server.pkix.useDefaultTrustAnchors=true + #- -Dcom.l7tech.bootstrap.autoTrustSslKey=trustAnchor,TrustedFor.SSL,TrustedFor.SAML_ISSUER + listenPorts: + harden: true + custom: + enabled: false + ports: [] + cwp: + enabled: true + properties: + - name: io.httpsHostAllowWildcard + value: "true" + - name: log.levels + value: | + com.l7tech.level = CONFIG + com.l7tech.server.policy.variable.ServerVariables.level = SEVERE + com.l7tech.external.assertions.odata.server.producer.jdbc.GenerateSqlQuery.level = SEVERE + com.l7tech.server.policy.assertion.ServerSetVariableAssertion.level = SEVERE + com.l7tech.external.assertions.comparison.server.ServerComparisonAssertion.level = SEVERE + - name: audit.setDetailLevel.FINE + value: 152 7101 7103 9648 9645 7026 7027 4155 150 4716 4114 6306 4100 9655 150 151 11000 4104 + system: + properties: |- + # Default Gateway system properties + # Configuration properties for shared state extensions. + com.l7tech.server.extension.sharedKeyValueStoreProvider=embeddedhazelcast + com.l7tech.server.extension.sharedCounterProvider=ssgdb + com.l7tech.server.extension.sharedClusterInfoProvider=ssgdb + # By default, FIPS module will block an RSA modulus from being used for encryption if it has been used for + # signing, or visa-versa. Set true to disable this default behaviour and remain backwards compatible. + com.l7tech.org.bouncycastle.rsa.allow_multi_use=true + # Specifies the type of Trust Store (JKS/PKCS12) provided by AdoptOpenJDK that is used by Gateway. + # Must be set correctly when Gateway is running in FIPS mode. If not specified it will default to PKCS12. + javax.net.ssl.trustStoreType=jks + com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 + # Additional properties go here + service: + # annotations: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP + ingress: + enabled: true + ingressClassName: nginx + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + tls: + - hosts: + - otk-ssg-dmz.brcmlabs.com + secretName: brcmlabs + rules: + - host: otk-ssg-dmz.brcmlabs.com + # containerSecurityContext: + # runAsNonRoot: true + # runAsUser: 1000669998 + # capabilities: + # drop: + # - ALL + # allowPrivilegeEscalation: false + # podSecurityContext: + # runAsUser: 1000669998 + # runAsGroup: 1000669998 + + diff --git a/example/gateway/otk/otk-ssg-internal.yaml b/example/gateway/otk/otk-ssg-internal.yaml new file mode 100644 index 00000000..786d60d4 --- /dev/null +++ b/example/gateway/otk/otk-ssg-internal.yaml @@ -0,0 +1,201 @@ +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-internal +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + updateStrategy: + type: rollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 2 + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-internal-tls-secret + enabled: true + alias: otk-internal-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: internal + dmzGatewayReference: otk-ssg-dmz + # DmzGatewayPort is used when the DMZ gateway is external (not managed by operator) + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + dmzGatewayPort: 9443 + # SyncIntervalSeconds determines how often DMZ and Internal gateways should update certificates + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for Internal key (used by OTK reconciliation) + internalKeySecret: otk-internal-tls-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + # A single secret containing all of the values defined here will be created + # if existingSecret is set the corresponding gateway, readOnly or admin will be omitted from the secret + # if no values are set, a secret will not be created or referenced and the deployment will be invalidated. + # existingSecret: otk-db-secret + gateway: + username: otk_user + password: otkUserPass + readOnly: + # username: readonly_user + username: readonly_user + password: readonly_userPass + admin: + # username: admin + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + #jdbcUrl: jdbc:mysql://:/ + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + autoscaling: + enabled: false + bundle: [] + repositoryReferences: [] + bootstrap: + script: + enabled: true + initContainers: [] + hazelcast: + external: false + endpoint: hazelcast.example.com:5701 + management: + secretName: gateway-secret + #username: admin + #password: 7layer + # Management port requires a separate service... + service: + enabled: false + #annotations: + # cloud.google.com/load-balancer-type: "Internal" + type: LoadBalancer + ports: + - name: management + port: 9443 + targetPort: 9443 + protocol: TCP + restman: + enabled: false + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + cluster: + #password: 7layer + hostname: gateway.brcmlabs.com + database: + enabled: false # this runs the gateway in dbbacked/ephemeral mode + # jdbcUrl: "jdbc:mysql://cluster1-haproxy.pxc.svc.cluster.local:3306/ssg" + # username: "gateway" + # password: "ACm8BDr3Rfk2Flx9V" + java: + jvmHeap: + calculate: true + percentage: 50 + default: 4g + extraArgs: + - -Dcom.l7tech.server.audit.message.saveToInternal=false + - -Dcom.l7tech.server.audit.admin.saveToInternal=false + - -Dcom.l7tech.server.audit.system.saveToInternal=false + - -Dcom.l7tech.server.audit.log.format=json + - -Djava.util.logging.config.file=/opt/SecureSpan/Gateway/node/default/etc/conf/log-override.properties + - -Dcom.l7tech.security.ssl.hostAllowWildcard=true + - -Dcom.l7tech.server.pkix.useDefaultTrustAnchors=true + #- -Dcom.l7tech.bootstrap.autoTrustSslKey=trustAnchor,TrustedFor.SSL,TrustedFor.SAML_ISSUER + listenPorts: + harden: true + custom: + enabled: false + ports: [] + cwp: + enabled: true + properties: + - name: io.httpsHostAllowWildcard + value: "true" + - name: log.levels + value: | + com.l7tech.level = CONFIG + com.l7tech.server.policy.variable.ServerVariables.level = SEVERE + com.l7tech.external.assertions.odata.server.producer.jdbc.GenerateSqlQuery.level = SEVERE + com.l7tech.server.policy.assertion.ServerSetVariableAssertion.level = SEVERE + com.l7tech.external.assertions.comparison.server.ServerComparisonAssertion.level = SEVERE + - name: audit.setDetailLevel.FINE + value: 152 7101 7103 9648 9645 7026 7027 4155 150 4716 4114 6306 4100 9655 150 151 11000 4104 + system: + properties: |- + # Default Gateway system properties + # Configuration properties for shared state extensions. + com.l7tech.server.extension.sharedKeyValueStoreProvider=embeddedhazelcast + com.l7tech.server.extension.sharedCounterProvider=ssgdb + com.l7tech.server.extension.sharedClusterInfoProvider=ssgdb + # By default, FIPS module will block an RSA modulus from being used for encryption if it has been used for + # signing, or visa-versa. Set true to disable this default behaviour and remain backwards compatible. + com.l7tech.org.bouncycastle.rsa.allow_multi_use=true + # Specifies the type of Trust Store (JKS/PKCS12) provided by AdoptOpenJDK that is used by Gateway. + # Must be set correctly when Gateway is running in FIPS mode. If not specified it will default to PKCS12. + javax.net.ssl.trustStoreType=jks + com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 + # Additional properties go here + service: + # annotations: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP + - name: management + port: 9443 + targetPort: 9443 + protocol: TCP + ingress: + enabled: true + ingressClassName: nginx + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + tls: + - hosts: + - otk-ssg-internal.brcmlabs.com + secretName: brcmlabs + rules: + - host: otk-ssg-internal.brcmlabs.com + # containerSecurityContext: + # runAsNonRoot: true + # runAsUser: 1000669998 + # capabilities: + # drop: + # - ALL + # allowPrivilegeEscalation: false + # podSecurityContext: + # runAsUser: 1000669998 + # runAsGroup: 1000669998 + + diff --git a/internal/controller/gateway/controller.go b/internal/controller/gateway/controller.go index 1ee34b0b..af83bb95 100644 --- a/internal/controller/gateway/controller.go +++ b/internal/controller/gateway/controller.go @@ -236,6 +236,11 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { req = append(req, creconcile.Request{NamespacedName: types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}}) } } + if gateway.Spec.App.Otk.Enabled { + if gateway.Spec.App.Otk.DmzKeySecret == a.GetName() || gateway.Spec.App.Otk.InternalKeySecret == a.GetName() { + req = append(req, creconcile.Request{NamespacedName: types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}}) + } + } } return req }), diff --git a/pkg/api/reconcile/l7api.go b/pkg/api/reconcile/l7api.go index e5a4d12b..c516b8e1 100644 --- a/pkg/api/reconcile/l7api.go +++ b/pkg/api/reconcile/l7api.go @@ -188,7 +188,7 @@ func deployL7ApiToGateway(ctx context.Context, params Params, gateway *v1.Gatewa } if tryRequest { - endpoint := pod.Status.PodIP + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" var errorMessage string status := SUCCESS name := gateway.Name @@ -264,7 +264,7 @@ func undeployL7ApiToGateway(ctx context.Context, params Params, gateway *v1.Gate } if tryRequest { - endpoint := pod.Status.PodIP + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" status := SUCCESS name := gateway.Name if gateway.Spec.App.Management.SecretName != "" { diff --git a/pkg/gateway/reconcile/cron.go b/pkg/gateway/reconcile/cron.go index 979c54f7..2de5da1c 100644 --- a/pkg/gateway/reconcile/cron.go +++ b/pkg/gateway/reconcile/cron.go @@ -72,16 +72,37 @@ func registerJobs(ctx context.Context, params Params) { if err != nil { params.Log.V(2).Info("otk policy sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) } - if params.Instance.Spec.App.Otk.Type == securityv1.OtkTypeDMZ || params.Instance.Spec.App.Otk.Type == securityv1.OtkTypeInternal { - _, err = s.Every(otkSyncInterval).Seconds().Tag(params.Instance.Name+"-"+params.Instance.Namespace+"-sync-otk-certificates").Do(syncOtkCertificates, ctx, params) - if err != nil { - params.Log.V(2).Info("otk certificate sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } - _, err = s.Every(otkSyncInterval).Seconds().Tag(params.Instance.Name+"-"+params.Instance.Namespace+"-sync-otk-certificate-secret").Do(manageCertificateSecrets, ctx, params) - if err != nil { - params.Log.V(2).Info("otk certificate secret sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } + + // Register certificate sync job for DMZ and Internal gateways + // Use SyncIntervalSeconds if specified, otherwise fall back to RuntimeSyncIntervalSeconds or default + certSyncInterval := otkSyncInterval + if params.Instance.Spec.App.Otk.SyncIntervalSeconds != 0 { + certSyncInterval = params.Instance.Spec.App.Otk.SyncIntervalSeconds + } + + _, err = s.Every(certSyncInterval).Seconds().Tag(params.Instance.Name+"-sync-otk-certificates").Do(syncOtkCertificates, ctx, params) + + if err != nil { + params.Log.V(2).Info("otk certificate sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) } + + // Register external keys sync job for certificate publishing between DMZ and Internal + _, err = s.Every(certSyncInterval).Seconds().Tag(params.Instance.Name+"-sync-otk-external-keys").Do(syncOtkExternalKeys, ctx, params) + + if err != nil { + params.Log.V(2).Info("otk external keys sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) + } + } +} + +func syncOtkExternalKeys(ctx context.Context, params Params) { + // Sync certificates between DMZ and Internal gateways via ExternalKeys + // This handles OTK certificate publishing (publishDmzCertToInternal, publishInternalCertToDmz) + err := ExternalKeys(ctx, params) + if err != nil { + params.Log.Error(err, "failed to sync OTK external keys certificates", "name", params.Instance.Name, "namespace", params.Instance.Namespace) + } else { + params.Log.V(2).Info("OTK external keys certificates synced", "name", params.Instance.Name, "namespace", params.Instance.Namespace, "interval", params.Instance.Spec.App.Otk.SyncIntervalSeconds) } } diff --git a/pkg/gateway/reconcile/externalkeys.go b/pkg/gateway/reconcile/externalkeys.go index de07af50..ddacf3ef 100644 --- a/pkg/gateway/reconcile/externalkeys.go +++ b/pkg/gateway/reconcile/externalkeys.go @@ -28,10 +28,37 @@ package reconcile import ( "context" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "strings" + + securityv1 "github.com/caapim/layer7-operator/api/v1" + "github.com/caapim/layer7-operator/internal/graphman" + "github.com/caapim/layer7-operator/pkg/util" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) func ExternalKeys(ctx context.Context, params Params) error { gateway := params.Instance + + // Handle OTK keys if OTK is enabled + if gateway.Spec.App.Otk.Enabled { + err := handleOtkKeys(ctx, params, gateway) + if err != nil { + params.Log.Error(err, "failed to handle OTK keys", "name", gateway.Name, "namespace", gateway.Namespace) + return err + } + } + + // Handle regular external keys if len(gateway.Spec.App.ExternalKeys) == 0 && len(gateway.Status.LastAppliedExternalKeys) == 0 { return nil } @@ -66,3 +93,741 @@ func ExternalKeys(ctx context.Context, params Params) error { return nil } + +func handleOtkKeys(ctx context.Context, params Params, gateway *securityv1.Gateway) error { + // Handle DMZ key updates only if there's an externalKey with otk: true referencing the same secret + if gateway.Spec.App.Otk.DmzKeySecret != "" && gateway.Spec.App.Otk.Type == securityv1.OtkTypeDMZ { + // Check if there's an externalKey with otk: true that references this secret + hasOtkExternalKey := false + for _, ek := range gateway.Spec.App.ExternalKeys { + if ek.Enabled && ek.Otk && ek.Name == gateway.Spec.App.Otk.DmzKeySecret { + hasOtkExternalKey = true + break + } + } + + // Only process if there's an externalKey with otk: true + if hasOtkExternalKey { + err := handleDmzKeyUpdate(ctx, params, gateway) + if err != nil { + params.Log.Error(err, "failed to handle DMZ key update", "name", gateway.Name, "namespace", gateway.Namespace) + return err + } + } else { + params.Log.V(2).Info("Skipping DMZ key update - no externalKey with otk: true found", "secret", gateway.Spec.App.Otk.DmzKeySecret) + } + } + + // Handle Internal key updates only if there's an externalKey with otk: true referencing the same secret + if gateway.Spec.App.Otk.InternalKeySecret != "" && gateway.Spec.App.Otk.Type == securityv1.OtkTypeInternal { + // Check if there's an externalKey with otk: true that references this secret + hasOtkExternalKey := false + for _, ek := range gateway.Spec.App.ExternalKeys { + if ek.Enabled && ek.Otk && ek.Name == gateway.Spec.App.Otk.InternalKeySecret { + hasOtkExternalKey = true + break + } + } + + // Only process if there's an externalKey with otk: true + if hasOtkExternalKey { + err := handleInternalKeyUpdate(ctx, params, gateway) + if err != nil { + params.Log.Error(err, "failed to handle Internal key update", "name", gateway.Name, "namespace", gateway.Namespace) + return err + } + } else { + params.Log.V(2).Info("Skipping Internal key update - no externalKey with otk: true found", "secret", gateway.Spec.App.Otk.InternalKeySecret) + } + } + + return nil +} + +func handleDmzKeyUpdate(ctx context.Context, params Params, gateway *securityv1.Gateway) error { + // Get DMZ key secret + dmzKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzKeySecret) + if err != nil { + if k8serrors.IsNotFound(err) { + params.Log.V(2).Info("DMZ key secret not found, skipping", "secret", gateway.Spec.App.Otk.DmzKeySecret) + return nil + } + return err + } + + // Check if operator managed (ephemeral mode) + isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled + + if isOperatorManaged { + // Update DMZ with the new key + err = updateDmzWithKey(ctx, params, gateway, dmzKeySecret) + if err != nil { + return fmt.Errorf("failed to update DMZ with key: %w", err) + } + } + + // Publish DMZ cert to Internal for Truststore & FIP User + if gateway.Spec.App.Otk.InternalOtkGatewayReference != "" { + err = publishDmzCertToInternal(ctx, params, gateway, dmzKeySecret) + if err != nil { + return fmt.Errorf("failed to publish DMZ cert to Internal: %w", err) + } + } + + return nil +} + +func handleInternalKeyUpdate(ctx context.Context, params Params, gateway *securityv1.Gateway) error { + // Get Internal key secret + internalKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalKeySecret) + if err != nil { + if k8serrors.IsNotFound(err) { + params.Log.V(2).Info("Internal key secret not found, skipping", "secret", gateway.Spec.App.Otk.InternalKeySecret) + return nil + } + return err + } + + // Check if operator managed (ephemeral mode) + isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled + + if isOperatorManaged { + // Update Internal with the new key + err = updateInternalWithKey(ctx, params, gateway, internalKeySecret) + if err != nil { + return fmt.Errorf("failed to update Internal with key: %w", err) + } + } + + // Publish Internal cert to DMZ for Truststore + if gateway.Spec.App.Otk.DmzOtkGatewayReference != "" { + err = publishInternalCertToDmz(ctx, params, gateway, internalKeySecret) + if err != nil { + return fmt.Errorf("failed to publish Internal cert to DMZ: %w", err) + } + } + + return nil +} + +func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Gateway, keySecret *corev1.Secret) error { + if keySecret.Type != corev1.SecretTypeTLS { + return fmt.Errorf("DMZ key secret must be of type kubernetes.io/tls") + } + + certData := keySecret.Data["tls.crt"] + keyData := keySecret.Data["tls.key"] + + if len(certData) == 0 || len(keyData) == 0 { + return fmt.Errorf("DMZ key secret must contain tls.crt and tls.key") + } + + // Extract certificate from chain + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format in DMZ key secret") + } + + // Use first certificate in chain + firstCert := crtStrings[0] + b, _ := pem.Decode([]byte(firstCert)) + if b == nil { + return fmt.Errorf("failed to decode certificate") + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + return fmt.Errorf("failed to parse certificate: %w", err) + } + + // Create Graphman key bundle + keySecretMap := []util.GraphmanKey{ + { + Name: crtX509.Subject.CommonName, + Crt: string(certData), + Key: string(keyData), + Alias: "otk-dmz-key", + UsageType: "SSL", + }, + } + + bundleBytes, err := util.ConvertX509ToGraphmanBundle(keySecretMap, []string{}) + if err != nil { + return fmt.Errorf("failed to convert key to bundle: %w", err) + } + + // Calculate checksum + dataBytes, _ := json.Marshal(&keySecretMap) + h := sha1.New() + h.Write(dataBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Get gateway secret for authentication + name := gateway.Name + if gateway.Spec.App.Management.SecretName != "" { + name = gateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) + if err != nil { + return err + } + + annotation := "security.brcmlabs.com/otk-dmz-key" + + if !gateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, params) + if err != nil { + return err + } + err = ReconcileEphemeralGateway(ctx, params, "otk dmz key", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) + if err != nil { + return err + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, params) + if err != nil { + return err + } + err = ReconcileDBGateway(ctx, params, "otk dmz key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) + if err != nil { + return err + } + } + + // Update cluster property otk.dmz.private_key.name after DMZ key is updated + //if err := updateDmzPrivateKeyClusterProperty(ctx, params, gateway, "otk-dmz-key"); err != nil { + // params.Log.V(2).Info("Failed to update DMZ private key cluster property", "error", err, "gateway", gateway.Name) + // // Don't fail the entire operation if cluster property update fails + //} + + return nil +} + +func updateInternalWithKey(ctx context.Context, params Params, gateway *securityv1.Gateway, keySecret *corev1.Secret) error { + if keySecret.Type != corev1.SecretTypeTLS { + return fmt.Errorf("Internal key secret must be of type kubernetes.io/tls") + } + + certData := keySecret.Data["tls.crt"] + keyData := keySecret.Data["tls.key"] + + if len(certData) == 0 || len(keyData) == 0 { + return fmt.Errorf("Internal key secret must contain tls.crt and tls.key") + } + + // Extract certificate from chain + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format in Internal key secret") + } + + // Use first certificate in chain + firstCert := crtStrings[0] + b, _ := pem.Decode([]byte(firstCert)) + if b == nil { + return fmt.Errorf("failed to decode certificate") + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + return fmt.Errorf("failed to parse certificate: %w", err) + } + + // Create Graphman key bundle + keySecretMap := []util.GraphmanKey{ + { + Name: crtX509.Subject.CommonName, + Crt: string(certData), + Key: string(keyData), + Alias: "otk-internal-key", + UsageType: "SSL", + }, + } + + bundleBytes, err := util.ConvertX509ToGraphmanBundle(keySecretMap, []string{}) + if err != nil { + return fmt.Errorf("failed to convert key to bundle: %w", err) + } + + // Calculate checksum + dataBytes, _ := json.Marshal(&keySecretMap) + h := sha1.New() + h.Write(dataBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Get gateway secret for authentication + name := gateway.Name + if gateway.Spec.App.Management.SecretName != "" { + name = gateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) + if err != nil { + return err + } + + annotation := "security.brcmlabs.com/otk-internal-key" + + if !gateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, params) + if err != nil { + return err + } + err = ReconcileEphemeralGateway(ctx, params, "otk internal key", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk internal key", bundleBytes) + if err != nil { + return err + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, params) + if err != nil { + return err + } + err = ReconcileDBGateway(ctx, params, "otk internal key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk internal key", bundleBytes) + if err != nil { + return err + } + } + + return nil +} + +func publishDmzCertToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret) error { + // Get Internal gateway + internalGateway := &securityv1.Gateway{} + err := params.Client.Get(ctx, types.NamespacedName{ + Name: gateway.Spec.App.Otk.InternalOtkGatewayReference, + Namespace: gateway.Namespace, + }, internalGateway) + + isExternalGateway := false + if err != nil { + if k8serrors.IsNotFound(err) { + // Gateway not found - check if it's external (port specified) + if gateway.Spec.App.Otk.InternalGatewayPort != 0 { + params.Log.V(2).Info("Internal gateway not found but port specified, treating as external", + "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, + "port", gateway.Spec.App.Otk.InternalGatewayPort) + isExternalGateway = true + } else { + params.Log.V(2).Info("Internal gateway not found and no port specified, skipping cert publish", + "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference) + return nil + } + } else { + return err + } + } + + certData := dmzKeySecret.Data["tls.crt"] + if len(certData) == 0 { + return fmt.Errorf("DMZ key secret must contain tls.crt") + } + + // Parse certificate + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format") + } + + bundle := graphman.Bundle{} + + // Add to TrustedCerts + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ + Name: crtX509.Subject.CommonName, + CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + TrustAnchor: true, + VerifyHostname: false, + RevocationCheckPolicyType: "USE_DEFAULT", + TrustedFor: []graphman.TrustedForType{ + "SSL", + "SIGNING_SERVER_CERTS", + }, + }) + + // Add to FIP Users + bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ + Name: crtX509.Subject.CommonName, + ProviderName: "otk-fips-provider", + SubjectDn: "cn=" + crtX509.Subject.CommonName, + CertBase64: base64.RawStdEncoding.EncodeToString(crtX509.Raw), + }) + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return err + } + + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // If gateway is external (not managed by operator), use specified port + if isExternalGateway { + // For external gateways, we can't use the standard reconciliation + // Log that external gateway support requires additional configuration + params.Log.V(2).Info("External Internal gateway detected, port will be used for connection", + "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, + "port", gateway.Spec.App.Otk.InternalGatewayPort) + // Note: External gateway connection would require additional implementation + // For now, we skip reconciliation for external gateways + return nil + } + + // Get Internal gateway secret + name := internalGateway.Name + if internalGateway.Spec.App.Management.SecretName != "" { + name = internalGateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) + if err != nil { + return err + } + + annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" + + internalParams := params + internalParams.Instance = internalGateway + + // Note: InternalGatewayPort is used when the gateway is external (not found in cluster) + // For operator-managed gateways, the gateway's own graphman port configuration is used + + if !internalGateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, internalParams) + if err != nil { + return err + } + err = ReconcileEphemeralGateway(ctx, internalParams, "otk certificates", *podList, internalGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) + if err != nil { + return err + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, internalParams) + if err != nil { + return err + } + err = ReconcileDBGateway(ctx, internalParams, "otk certificates", *gatewayDeployment, internalGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) + if err != nil { + return err + } + } + + return nil +} + +func publishInternalCertToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway, internalKeySecret *corev1.Secret) error { + // Get DMZ gateway + dmzGateway := &securityv1.Gateway{} + err := params.Client.Get(ctx, types.NamespacedName{ + Name: gateway.Spec.App.Otk.DmzOtkGatewayReference, + Namespace: gateway.Namespace, + }, dmzGateway) + + isExternalGateway := false + if err != nil { + if k8serrors.IsNotFound(err) { + // Gateway not found - check if it's external (port specified) + if gateway.Spec.App.Otk.DmzGatewayPort != 0 { + params.Log.V(2).Info("DMZ gateway not found but port specified, treating as external", + "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, + "port", gateway.Spec.App.Otk.DmzGatewayPort) + isExternalGateway = true + } else { + params.Log.V(2).Info("DMZ gateway not found and no port specified, skipping cert publish", + "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference) + return nil + } + } else { + return err + } + } + + certData := internalKeySecret.Data["tls.crt"] + if len(certData) == 0 { + return fmt.Errorf("Internal key secret must contain tls.crt") + } + + // Parse certificate + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format") + } + + bundle := graphman.Bundle{} + + // Add to TrustedCerts + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ + Name: crtX509.Subject.CommonName, + CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + TrustAnchor: true, + VerifyHostname: false, + RevocationCheckPolicyType: "USE_DEFAULT", + TrustedFor: []graphman.TrustedForType{ + "SSL", + "SIGNING_SERVER_CERTS", + }, + }) + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return err + } + + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // If gateway is external (not managed by operator), use specified port + if isExternalGateway { + // For external gateways, we can't use the standard reconciliation + // Log that external gateway support requires additional configuration + params.Log.V(2).Info("External DMZ gateway detected, port will be used for connection", + "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, + "port", gateway.Spec.App.Otk.DmzGatewayPort) + // Note: External gateway connection would require additional implementation + // For now, we skip reconciliation for external gateways + return nil + } + + // Get DMZ gateway secret + name := dmzGateway.Name + if dmzGateway.Spec.App.Management.SecretName != "" { + name = dmzGateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) + if err != nil { + return err + } + + annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" + + dmzParams := params + dmzParams.Instance = dmzGateway + + // Note: DmzGatewayPort is used when the gateway is external (not found in cluster) + // For operator-managed gateways, the gateway's own graphman port configuration is used + + if !dmzGateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, dmzParams) + if err != nil { + return err + } + err = ReconcileEphemeralGateway(ctx, dmzParams, "otk certificates", *podList, dmzGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) + if err != nil { + return err + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) + if err != nil { + return err + } + err = ReconcileDBGateway(ctx, dmzParams, "otk certificates", *gatewayDeployment, dmzGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) + if err != nil { + return err + } + } + + return nil +} + +// updateDmzPrivateKeyClusterProperty updates the cluster property otk.dmz.private_key.name +// with the DMZ private key name. This is called after the DMZ key is successfully updated. +func updateDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gateway *securityv1.Gateway, keyName string) error { + // Only update cluster property for DMZ gateway type + if gateway.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { + return nil + } + + // Get or create the cluster properties ConfigMap + cmName := gateway.Name + "-cwp-bundle" + cm, err := getGatewayConfigMap(ctx, params, cmName) + if err != nil { + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("failed to get cluster properties ConfigMap: %w", err) + } + // ConfigMap doesn't exist, create it with the property + return createDmzPrivateKeyClusterProperty(ctx, params, gateway, keyName, cmName) + } + + // Parse existing bundle + bundle := graphman.Bundle{} + bundleJSON := cm.Data["cwp.json"] + if bundleJSON == "" { + // Empty bundle, create new one + return createDmzPrivateKeyClusterProperty(ctx, params, gateway, keyName, cmName) + } + + err = json.Unmarshal([]byte(bundleJSON), &bundle) + if err != nil { + return fmt.Errorf("failed to parse cluster properties bundle: %w", err) + } + + // Initialize bundle properties if nil + if bundle.Properties == nil { + bundle.Properties = &graphman.BundleProperties{ + Mappings: graphman.BundleMappings{}, + } + } + + // Check if property already exists and update it, or add new one + propertyName := "otk.dmz.private_key.name" + found := false + for _, cwp := range bundle.ClusterProperties { + if cwp.Name == propertyName { + cwp.Value = keyName + found = true + break + } + } + + if !found { + // Add new cluster property + bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ + Name: propertyName, + Value: keyName, + }) + } + + // Marshal bundle back to JSON + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return fmt.Errorf("failed to marshal cluster properties bundle: %w", err) + } + + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Update ConfigMap + cm.Data["cwp.json"] = string(bundleBytes) + if cm.ObjectMeta.Annotations == nil { + cm.ObjectMeta.Annotations = make(map[string]string) + } + cm.ObjectMeta.Annotations["checksum/data"] = sha1Sum + + err = params.Client.Update(ctx, cm) + if err != nil { + return fmt.Errorf("failed to update cluster properties ConfigMap: %w", err) + } + + params.Log.V(2).Info("Updated cluster property ConfigMap", "property", propertyName, "value", keyName, "gateway", gateway.Name) + + // Apply the cluster property using the existing mechanism + gwUpdReq, err := NewGwUpdateRequest( + ctx, + gateway, + params, + WithBundleType(BundleTypeClusterProp), + ) + if err != nil { + return fmt.Errorf("failed to create gateway update request: %w", err) + } + + err = SyncGateway(ctx, params, *gwUpdReq) + if err != nil { + return fmt.Errorf("failed to sync cluster property: %w", err) + } + + params.Log.V(2).Info("Applied cluster property", "property", propertyName, "value", keyName, "gateway", gateway.Name) + + return nil +} + +// createDmzPrivateKeyClusterProperty creates a new cluster properties ConfigMap with the DMZ private key property +func createDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gateway *securityv1.Gateway, keyName string, cmName string) error { + // Create new bundle with the property + bundle := graphman.Bundle{ + ClusterProperties: []*graphman.ClusterPropertyInput{ + { + Name: "otk.dmz.private_key.name", + Value: keyName, + }, + }, + Properties: &graphman.BundleProperties{ + Mappings: graphman.BundleMappings{}, + }, + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return fmt.Errorf("failed to marshal cluster properties bundle: %w", err) + } + + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Create ConfigMap + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmName, + Namespace: gateway.Namespace, + Annotations: map[string]string{ + "checksum/data": sha1Sum, + }, + }, + Data: map[string]string{ + "cwp.json": string(bundleBytes), + }, + } + + // Set controller reference + if err := controllerutil.SetControllerReference(gateway, cm, params.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) + } + + err = params.Client.Create(ctx, cm) + if err != nil { + return fmt.Errorf("failed to create cluster properties ConfigMap: %w", err) + } + + params.Log.V(2).Info("Created cluster property ConfigMap", "property", "otk.dmz.private_key.name", "value", keyName, "gateway", gateway.Name) + + // Apply the cluster property using the existing mechanism + gwUpdReq, err := NewGwUpdateRequest( + ctx, + gateway, + params, + WithBundleType(BundleTypeClusterProp), + ) + if err != nil { + return fmt.Errorf("failed to create gateway update request: %w", err) + } + + err = SyncGateway(ctx, params, *gwUpdReq) + if err != nil { + return fmt.Errorf("failed to sync cluster property: %w", err) + } + + params.Log.V(2).Info("Applied cluster property", "property", "otk.dmz.private_key.name", "value", keyName, "gateway", gateway.Name) + + return nil +} diff --git a/pkg/gateway/reconcile/gateway.go b/pkg/gateway/reconcile/gateway.go index f01acbc3..24a1ed28 100644 --- a/pkg/gateway/reconcile/gateway.go +++ b/pkg/gateway/reconcile/gateway.go @@ -499,7 +499,8 @@ func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params for _, k := range gateway.Status.LastAppliedExternalKeys { found := false for _, ek := range gateway.Spec.App.ExternalKeys { - if k == ek.Alias && ek.Enabled { + if k == ek.Alias && ek.Enabled && !ek.Otk { + // Only process non-OTK keys in regular external keys flow found = true } } @@ -511,7 +512,8 @@ func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params var sha1Sum string for _, externalKey := range gateway.Spec.App.ExternalKeys { - if externalKey.Enabled { + if externalKey.Enabled && !externalKey.Otk { + // Skip keys with otk: true - they are handled separately by OTK reconciliation secret, err := getGatewaySecret(ctx, params, externalKey.Name) if err != nil { return nil, err @@ -1532,7 +1534,7 @@ func updateGatewayDeployment(ctx context.Context, params Params, gwUpdReq *Gatew leaderAvailable := false for _, pod := range gwUpdReq.podList.Items { if pod.ObjectMeta.Labels["management-access"] == "leader" { - endpoint = podIP(pod.Status.PodIP) + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" + endpoint = "127.0.0.1" + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" leaderAvailable = true } } @@ -1838,7 +1840,8 @@ func updateGatewayPods(ctx context.Context, params Params, gwUpdReq *GatewayUpda if update && ready { updateStatus = true - endpoint := podIP(pod.Status.PodIP) + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" + endpoint := "127.0.0.1" + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" + requestCacheEntry := pod.Name + "-" + gwUpdReq.cacheEntry syncRequest, err := syncCache.Read(requestCacheEntry) tryRequest := true @@ -2117,7 +2120,7 @@ func ReconcileEphemeralGateway(ctx context.Context, params Params, kind string, if update && ready { updateStatus = true - endpoint := podIP(pod.Status.PodIP) + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" requestCacheEntry := pod.Name + "-" + gateway.Name + "-" + name + "-" + sha1Sum syncRequest, err := syncCache.Read(requestCacheEntry) diff --git a/pkg/gateway/reconcile/l7otkcertificates.go b/pkg/gateway/reconcile/l7otkcertificates.go index c290f3f5..1f664f80 100644 --- a/pkg/gateway/reconcile/l7otkcertificates.go +++ b/pkg/gateway/reconcile/l7otkcertificates.go @@ -27,15 +27,10 @@ package reconcile import ( "context" - "crypto/tls" "encoding/base64" "encoding/json" - "strconv" - securityv1 "github.com/caapim/layer7-operator/api/v1" "github.com/caapim/layer7-operator/internal/graphman" - "github.com/caapim/layer7-operator/pkg/gateway" - corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" ) @@ -143,115 +138,3 @@ func applyOtkCertificates(ctx context.Context, params Params, gateway *securityv return nil } - -func manageCertificateSecrets(ctx context.Context, params Params) { - gw := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{Name: params.Instance.Name, Namespace: params.Instance.Namespace}, gw) - if err != nil && k8serrors.IsNotFound(err) { - params.Log.Error(err, "gateway not found", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - _ = removeJob(params.Instance.Name + "-sync-otk-certificate-secret") - return - } - - if !gw.Spec.App.Otk.Enabled { - _ = removeJob(params.Instance.Name + "-sync-otk-certificate-secret") - return - } - params.Instance = gw - internalGatewayPort := 9443 - defaultOtkPort := 8443 - rawInternalCertList := map[string][]byte{} - rawDMZCertList := map[string][]byte{} - desiredSecrets := []*corev1.Secret{} - if gw.Spec.App.Management.Graphman.DynamicSyncPort != 0 { - internalGatewayPort = gw.Spec.App.Management.Graphman.DynamicSyncPort - - } - - if gw.Spec.App.Otk.InternalGatewayPort != 0 { - internalGatewayPort = gw.Spec.App.Otk.InternalGatewayPort - } - - if gw.Spec.App.Otk.OTKPort != 0 { - defaultOtkPort = gw.Spec.App.Otk.OTKPort - } - podList, err := getGatewayPods(ctx, params) - if err != nil { - params.Log.Error(err, "failed to retrieve gateway pods", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - return - } - - for _, pod := range podList.Items { - for _, containerStatus := range pod.Status.ContainerStatuses { - if containerStatus.Name == "gateway" { - if !containerStatus.Ready { - params.Log.V(2).Info("pod not ready", "pod", pod.Name, "name", params.Instance.Name, "namespace", params.Instance.Namespace) - return - } - } - } - - switch gw.Spec.App.Otk.Type { - case securityv1.OtkTypeDMZ: - rawCert, err := retrieveCertificate(pod.Status.PodIP, strconv.Itoa(defaultOtkPort)) - if err != nil { - params.Log.Error(err, "failed to retrieve certificate", "pod", pod.Name, "name", params.Instance.Name, "namespace", params.Instance.Namespace) - return - } - if len(rawDMZCertList) > 0 { - for _, cert := range rawDMZCertList { - if string(rawCert) != string(cert) { - rawDMZCertList[pod.Name] = rawCert - } - } - } else { - rawDMZCertList[pod.Name] = rawCert - } - case securityv1.OtkTypeInternal: - rawCert, err := retrieveCertificate(pod.Status.PodIP, strconv.Itoa(internalGatewayPort)) - if err != nil { - params.Log.Error(err, "failed to retrieve certificate", "pod", pod.Name, "name", params.Instance.Name, "namespace", params.Instance.Namespace) - return - } - if len(rawInternalCertList) > 0 { - for _, cert := range rawInternalCertList { - if string(rawCert) != string(cert) { - rawInternalCertList[pod.Name] = rawCert - } - } - } else { - rawInternalCertList[pod.Name] = rawCert - } - - } - } - - if gw.Spec.App.Otk.Type == securityv1.OtkTypeDMZ && len(rawDMZCertList) > 0 { - desiredSecrets = append(desiredSecrets, gateway.NewOtkCertificateSecret(gw, gw.Name+"-otk-dmz-certificates", rawDMZCertList)) - } - - if gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal && len(rawInternalCertList) > 0 { - desiredSecrets = append(desiredSecrets, gateway.NewOtkCertificateSecret(gw, gw.Name+"-otk-internal-certificates", rawInternalCertList)) - } - - err = reconcileSecrets(ctx, params, desiredSecrets) - if err != nil { - params.Log.Error(err, "failed to reconcile otk certificates", "Name", gw.Name, "namespace", gw.Namespace) - return - } - -} - -func retrieveCertificate(host string, port string) ([]byte, error) { - conf := &tls.Config{ - InsecureSkipVerify: true, - } - - conn, err := tls.Dial("tcp", host+":"+port, conf) - if err != nil { - return nil, err - } - defer conn.Close() - cert := conn.ConnectionState().PeerCertificates[0].Raw - return cert, nil -} From a53e1077d24117b2de959afd6a87a24afde83af4 Mon Sep 17 00:00:00 2001 From: as673366 Date: Mon, 22 Dec 2025 22:08:18 +0530 Subject: [PATCH 02/11] reverting unintenteded changes --- pkg/api/reconcile/l7api.go | 4 ++-- pkg/gateway/reconcile/gateway.go | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/api/reconcile/l7api.go b/pkg/api/reconcile/l7api.go index c516b8e1..e5a4d12b 100644 --- a/pkg/api/reconcile/l7api.go +++ b/pkg/api/reconcile/l7api.go @@ -188,7 +188,7 @@ func deployL7ApiToGateway(ctx context.Context, params Params, gateway *v1.Gatewa } if tryRequest { - endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := pod.Status.PodIP + ":" + strconv.Itoa(graphmanPort) + "/graphman" var errorMessage string status := SUCCESS name := gateway.Name @@ -264,7 +264,7 @@ func undeployL7ApiToGateway(ctx context.Context, params Params, gateway *v1.Gate } if tryRequest { - endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := pod.Status.PodIP + ":" + strconv.Itoa(graphmanPort) + "/graphman" status := SUCCESS name := gateway.Name if gateway.Spec.App.Management.SecretName != "" { diff --git a/pkg/gateway/reconcile/gateway.go b/pkg/gateway/reconcile/gateway.go index 24a1ed28..f2ca4434 100644 --- a/pkg/gateway/reconcile/gateway.go +++ b/pkg/gateway/reconcile/gateway.go @@ -1534,7 +1534,7 @@ func updateGatewayDeployment(ctx context.Context, params Params, gwUpdReq *Gatew leaderAvailable := false for _, pod := range gwUpdReq.podList.Items { if pod.ObjectMeta.Labels["management-access"] == "leader" { - endpoint = "127.0.0.1" + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" + endpoint = podIP(pod.Status.PodIP) + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" leaderAvailable = true } } @@ -1840,8 +1840,7 @@ func updateGatewayPods(ctx context.Context, params Params, gwUpdReq *GatewayUpda if update && ready { updateStatus = true - endpoint := "127.0.0.1" + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" - + endpoint := podIP(pod.Status.PodIP) + ":" + strconv.Itoa(gwUpdReq.graphmanPort) + "/graphman" requestCacheEntry := pod.Name + "-" + gwUpdReq.cacheEntry syncRequest, err := syncCache.Read(requestCacheEntry) tryRequest := true @@ -2120,7 +2119,7 @@ func ReconcileEphemeralGateway(ctx context.Context, params Params, kind string, if update && ready { updateStatus = true - endpoint := "127.0.0.1" + ":" + strconv.Itoa(graphmanPort) + "/graphman" + endpoint := podIP(pod.Status.PodIP) + ":" + strconv.Itoa(graphmanPort) + "/graphman" requestCacheEntry := pod.Name + "-" + gateway.Name + "-" + name + "-" + sha1Sum syncRequest, err := syncCache.Read(requestCacheEntry) From 6ef94c1b1cfba9f28147bbeff54d0f19a82ec894 Mon Sep 17 00:00:00 2001 From: as673366 Date: Tue, 23 Dec 2025 10:39:05 +0530 Subject: [PATCH 03/11] segregating certificate & key sync operations --- pkg/gateway/reconcile/externalkeys.go | 346 ++-------- pkg/gateway/reconcile/l7otkcertificates.go | 727 +++++++++++++++++++-- 2 files changed, 742 insertions(+), 331 deletions(-) diff --git a/pkg/gateway/reconcile/externalkeys.go b/pkg/gateway/reconcile/externalkeys.go index ddacf3ef..8a76eca1 100644 --- a/pkg/gateway/reconcile/externalkeys.go +++ b/pkg/gateway/reconcile/externalkeys.go @@ -30,7 +30,6 @@ import ( "context" "crypto/sha1" "crypto/x509" - "encoding/base64" "encoding/json" "encoding/pem" "fmt" @@ -42,7 +41,6 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) @@ -159,21 +157,13 @@ func handleDmzKeyUpdate(ctx context.Context, params Params, gateway *securityv1. isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled if isOperatorManaged { - // Update DMZ with the new key + // Update DMZ with the new key (key sync only, cert publishing handled by syncOtkCertificates) err = updateDmzWithKey(ctx, params, gateway, dmzKeySecret) if err != nil { return fmt.Errorf("failed to update DMZ with key: %w", err) } } - // Publish DMZ cert to Internal for Truststore & FIP User - if gateway.Spec.App.Otk.InternalOtkGatewayReference != "" { - err = publishDmzCertToInternal(ctx, params, gateway, dmzKeySecret) - if err != nil { - return fmt.Errorf("failed to publish DMZ cert to Internal: %w", err) - } - } - return nil } @@ -192,21 +182,13 @@ func handleInternalKeyUpdate(ctx context.Context, params Params, gateway *securi isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled if isOperatorManaged { - // Update Internal with the new key + // Update Internal with the new key (key sync only, cert publishing handled by syncOtkCertificates) err = updateInternalWithKey(ctx, params, gateway, internalKeySecret) if err != nil { return fmt.Errorf("failed to update Internal with key: %w", err) } } - // Publish Internal cert to DMZ for Truststore - if gateway.Spec.App.Otk.DmzOtkGatewayReference != "" { - err = publishInternalCertToDmz(ctx, params, gateway, internalKeySecret) - if err != nil { - return fmt.Errorf("failed to publish Internal cert to DMZ: %w", err) - } - } - return nil } @@ -273,31 +255,51 @@ func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Ga annotation := "security.brcmlabs.com/otk-dmz-key" + // Check if DMZ key was already applied (to determine if update is needed) + keyWasUpdated := false if !gateway.Spec.App.Management.Database.Enabled { podList, err := getGatewayPods(ctx, params) if err != nil { return err } + // Check current annotation value before update + currentSha1Sum := "" + for _, pod := range podList.Items { + if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { + currentSha1Sum = val + break + } + } err = ReconcileEphemeralGateway(ctx, params, "otk dmz key", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) if err != nil { return err } + // Key was updated if sha1Sum changed + keyWasUpdated = (currentSha1Sum != sha1Sum) } else { gatewayDeployment, err := getGatewayDeployment(ctx, params) if err != nil { return err } + // Check current annotation value before update + currentSha1Sum := gatewayDeployment.ObjectMeta.Annotations[annotation] err = ReconcileDBGateway(ctx, params, "otk dmz key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) if err != nil { return err } + // Key was updated if sha1Sum changed (ReconcileDBGateway returns early if already applied) + keyWasUpdated = (currentSha1Sum != sha1Sum) } - // Update cluster property otk.dmz.private_key.name after DMZ key is updated - //if err := updateDmzPrivateKeyClusterProperty(ctx, params, gateway, "otk-dmz-key"); err != nil { - // params.Log.V(2).Info("Failed to update DMZ private key cluster property", "error", err, "gateway", gateway.Name) - // // Don't fail the entire operation if cluster property update fails - //} + // Update cluster property only if DMZ key was updated + if keyWasUpdated { + if err := updateDmzPrivateKeyClusterProperty(ctx, params, gateway, "otk-dmz-key"); err != nil { + params.Log.V(2).Info("Failed to update DMZ private key cluster property", "error", err, "gateway", gateway.Name) + // Don't fail the entire operation if cluster property update fails + } + } else if !keyWasUpdated { + params.Log.V(2).Info("DMZ key was not updated, skipping cluster property update", "gateway", gateway.Name) + } return nil } @@ -388,299 +390,68 @@ func updateInternalWithKey(ctx context.Context, params Params, gateway *security return nil } -func publishDmzCertToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret) error { - // Get Internal gateway - internalGateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{ - Name: gateway.Spec.App.Otk.InternalOtkGatewayReference, - Namespace: gateway.Namespace, - }, internalGateway) - - isExternalGateway := false - if err != nil { - if k8serrors.IsNotFound(err) { - // Gateway not found - check if it's external (port specified) - if gateway.Spec.App.Otk.InternalGatewayPort != 0 { - params.Log.V(2).Info("Internal gateway not found but port specified, treating as external", - "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, - "port", gateway.Spec.App.Otk.InternalGatewayPort) - isExternalGateway = true - } else { - params.Log.V(2).Info("Internal gateway not found and no port specified, skipping cert publish", - "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference) - return nil - } - } else { - return err - } - } - - certData := dmzKeySecret.Data["tls.crt"] - if len(certData) == 0 { - return fmt.Errorf("DMZ key secret must contain tls.crt") - } - - // Parse certificate - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format") - } - - bundle := graphman.Bundle{} - - // Add to TrustedCerts - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: crtX509.Subject.CommonName, - CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, - }) - - // Add to FIP Users - bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ - Name: crtX509.Subject.CommonName, - ProviderName: "otk-fips-provider", - SubjectDn: "cn=" + crtX509.Subject.CommonName, - CertBase64: base64.RawStdEncoding.EncodeToString(crtX509.Raw), - }) - } - - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return err - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // If gateway is external (not managed by operator), use specified port - if isExternalGateway { - // For external gateways, we can't use the standard reconciliation - // Log that external gateway support requires additional configuration - params.Log.V(2).Info("External Internal gateway detected, port will be used for connection", - "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, - "port", gateway.Spec.App.Otk.InternalGatewayPort) - // Note: External gateway connection would require additional implementation - // For now, we skip reconciliation for external gateways - return nil - } - - // Get Internal gateway secret - name := internalGateway.Name - if internalGateway.Spec.App.Management.SecretName != "" { - name = internalGateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - if err != nil { - return err - } - - annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" - - internalParams := params - internalParams.Instance = internalGateway - - // Note: InternalGatewayPort is used when the gateway is external (not found in cluster) - // For operator-managed gateways, the gateway's own graphman port configuration is used - - if !internalGateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, internalParams) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, internalParams, "otk certificates", *podList, internalGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, internalParams) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, internalParams, "otk certificates", *gatewayDeployment, internalGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) - if err != nil { - return err - } +// checkClusterPropertyExists checks if the cluster property exists in the ConfigMap +func checkClusterPropertyExists(ctx context.Context, params Params, gateway *securityv1.Gateway, propertyName string) bool { + // Only check for DMZ gateway type + if gateway.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { + return false } - return nil -} - -func publishInternalCertToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway, internalKeySecret *corev1.Secret) error { - // Get DMZ gateway - dmzGateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{ - Name: gateway.Spec.App.Otk.DmzOtkGatewayReference, - Namespace: gateway.Namespace, - }, dmzGateway) - - isExternalGateway := false + // Get the cluster properties ConfigMap + cmName := gateway.Name + "-cwp-bundle" + cm, err := getGatewayConfigMap(ctx, params, cmName) if err != nil { - if k8serrors.IsNotFound(err) { - // Gateway not found - check if it's external (port specified) - if gateway.Spec.App.Otk.DmzGatewayPort != 0 { - params.Log.V(2).Info("DMZ gateway not found but port specified, treating as external", - "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, - "port", gateway.Spec.App.Otk.DmzGatewayPort) - isExternalGateway = true - } else { - params.Log.V(2).Info("DMZ gateway not found and no port specified, skipping cert publish", - "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference) - return nil - } - } else { - return err - } + return false } - certData := internalKeySecret.Data["tls.crt"] - if len(certData) == 0 { - return fmt.Errorf("Internal key secret must contain tls.crt") - } - - // Parse certificate - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format") + // Parse existing bundle + bundleJSON := cm.Data["cwp.json"] + if bundleJSON == "" { + return false } bundle := graphman.Bundle{} - - // Add to TrustedCerts - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: crtX509.Subject.CommonName, - CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, - }) - } - - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return err - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // If gateway is external (not managed by operator), use specified port - if isExternalGateway { - // For external gateways, we can't use the standard reconciliation - // Log that external gateway support requires additional configuration - params.Log.V(2).Info("External DMZ gateway detected, port will be used for connection", - "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, - "port", gateway.Spec.App.Otk.DmzGatewayPort) - // Note: External gateway connection would require additional implementation - // For now, we skip reconciliation for external gateways - return nil - } - - // Get DMZ gateway secret - name := dmzGateway.Name - if dmzGateway.Spec.App.Management.SecretName != "" { - name = dmzGateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) + err = json.Unmarshal([]byte(bundleJSON), &bundle) if err != nil { - return err + return false } - annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" - - dmzParams := params - dmzParams.Instance = dmzGateway - - // Note: DmzGatewayPort is used when the gateway is external (not found in cluster) - // For operator-managed gateways, the gateway's own graphman port configuration is used - - if !dmzGateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, dmzParams) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, dmzParams, "otk certificates", *podList, dmzGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, dmzParams, "otk certificates", *gatewayDeployment, dmzGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) - if err != nil { - return err + // Check if property exists + for _, cwp := range bundle.ClusterProperties { + if cwp.Name == propertyName { + return true } } - return nil + return false } // updateDmzPrivateKeyClusterProperty updates the cluster property otk.dmz.private_key.name -// with the DMZ private key name. This is called after the DMZ key is successfully updated. +// with the DMZ private key name. This function only updates the property if it exists. +// It does not create the property if it doesn't exist. func updateDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gateway *securityv1.Gateway, keyName string) error { // Only update cluster property for DMZ gateway type if gateway.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { return nil } - // Get or create the cluster properties ConfigMap + // Get the cluster properties ConfigMap cmName := gateway.Name + "-cwp-bundle" cm, err := getGatewayConfigMap(ctx, params, cmName) if err != nil { if !k8serrors.IsNotFound(err) { return fmt.Errorf("failed to get cluster properties ConfigMap: %w", err) } - // ConfigMap doesn't exist, create it with the property - return createDmzPrivateKeyClusterProperty(ctx, params, gateway, keyName, cmName) + // ConfigMap doesn't exist, property doesn't exist - skip update + return fmt.Errorf("cluster property ConfigMap does not exist") } // Parse existing bundle bundle := graphman.Bundle{} bundleJSON := cm.Data["cwp.json"] if bundleJSON == "" { - // Empty bundle, create new one - return createDmzPrivateKeyClusterProperty(ctx, params, gateway, keyName, cmName) + // Empty bundle, property doesn't exist - skip update + return fmt.Errorf("cluster property bundle is empty") } err = json.Unmarshal([]byte(bundleJSON), &bundle) @@ -695,7 +466,7 @@ func updateDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gate } } - // Check if property already exists and update it, or add new one + // Check if property exists and update it propertyName := "otk.dmz.private_key.name" found := false for _, cwp := range bundle.ClusterProperties { @@ -707,11 +478,8 @@ func updateDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gate } if !found { - // Add new cluster property - bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ - Name: propertyName, - Value: keyName, - }) + // Property doesn't exist - skip update (only update, don't create) + return fmt.Errorf("cluster property %s does not exist", propertyName) } // Marshal bundle back to JSON diff --git a/pkg/gateway/reconcile/l7otkcertificates.go b/pkg/gateway/reconcile/l7otkcertificates.go index 1f664f80..d8703d70 100644 --- a/pkg/gateway/reconcile/l7otkcertificates.go +++ b/pkg/gateway/reconcile/l7otkcertificates.go @@ -27,10 +27,18 @@ package reconcile import ( "context" + "crypto/sha1" + "crypto/x509" "encoding/base64" "encoding/json" + "encoding/pem" + "fmt" + "strings" + securityv1 "github.com/caapim/layer7-operator/api/v1" "github.com/caapim/layer7-operator/internal/graphman" + "github.com/caapim/layer7-operator/pkg/util" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" ) @@ -49,88 +57,465 @@ func syncOtkCertificates(ctx context.Context, params Params) { return } - err = applyOtkCertificates(ctx, params, gateway) + // Publish DMZ certs to Internal Gateway when DMZ key is updated + if gateway.Spec.App.Otk.Type == securityv1.OtkTypeDMZ && gateway.Spec.App.Otk.DmzKeySecret != "" { + err = publishDmzCertificatesToInternal(ctx, params, gateway) + if err != nil { + params.Log.V(2).Info("failed to publish DMZ certificates to Internal", "name", gateway.Name, "namespace", gateway.Namespace, "error", err.Error()) + } + } + + // Publish Internal certs to DMZ Gateway when Internal key is updated + if gateway.Spec.App.Otk.Type == securityv1.OtkTypeInternal && gateway.Spec.App.Otk.InternalKeySecret != "" { + err = publishInternalCertificatesToDmz(ctx, params, gateway) + if err != nil { + params.Log.V(2).Info("failed to publish Internal certificates to DMZ", "name", gateway.Name, "namespace", gateway.Namespace, "error", err.Error()) + } + } +} + +// publishDmzCertificatesToInternal publishes DMZ certificates to Internal gateway when DMZ key is updated +// Handles ephemeral, DB-backed, and external gateways +func publishDmzCertificatesToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway) error { + // Check if Internal gateway reference is specified + if gateway.Spec.App.Otk.InternalOtkGatewayReference == "" { + return nil + } + + // Get DMZ key secret + dmzKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzKeySecret) if err != nil { - params.Log.Info("failed to reconcile otk certificates", "name", gateway.Name, "namespace", gateway.Namespace, "error", err.Error()) + if k8serrors.IsNotFound(err) { + params.Log.V(2).Info("DMZ key secret not found, skipping cert publish", "secret", gateway.Spec.App.Otk.DmzKeySecret) + return nil + } + return err + } + + // Check if key was updated by comparing annotation + annotation := "security.brcmlabs.com/otk-dmz-key" + currentSha1Sum := "" + + if !gateway.Spec.App.Management.Database.Enabled { + // Ephemeral gateway - check pod annotations + podList, err := getGatewayPods(ctx, params) + if err != nil { + return err + } + for _, pod := range podList.Items { + if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { + currentSha1Sum = val + break + } + } + } else { + // DB-backed gateway - check deployment annotations + gatewayDeployment, err := getGatewayDeployment(ctx, params) + if err != nil { + return err + } + currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] + } + + // Calculate current key checksum + certData := dmzKeySecret.Data["tls.crt"] + keyData := dmzKeySecret.Data["tls.key"] + if len(certData) == 0 || len(keyData) == 0 { + return fmt.Errorf("DMZ key secret must contain tls.crt and tls.key") } + + keySecretMap := []struct { + Name string + Crt string + Key string + Alias string + UsageType string + }{ + { + Name: "dmz-key", + Crt: string(certData), + Key: string(keyData), + Alias: "otk-dmz-key", + UsageType: "SSL", + }, + } + + dataBytes, _ := json.Marshal(&keySecretMap) + h := sha1.New() + h.Write(dataBytes) + newSha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Only publish if key was updated + if currentSha1Sum == newSha1Sum { + params.Log.V(2).Info("DMZ key not updated, skipping cert publish", "gateway", gateway.Name) + return nil + } + + // Publish DMZ cert to Internal (handles ephemeral, DB-backed, and external gateways) + return publishDmzCertToInternal(ctx, params, gateway, dmzKeySecret) } -func applyOtkCertificates(ctx context.Context, params Params, gateway *securityv1.Gateway) error { +// publishInternalCertificatesToDmz publishes Internal certificates to DMZ gateway when Internal key is updated +// Handles ephemeral, DB-backed, and external gateways +func publishInternalCertificatesToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway) error { + // Check if DMZ gateway reference is specified + if gateway.Spec.App.Otk.DmzOtkGatewayReference == "" { + return nil + } - bundle := graphman.Bundle{} - annotation := "" - sha1Sum := "" + // Get Internal key secret + internalKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalKeySecret) + if err != nil { + if k8serrors.IsNotFound(err) { + params.Log.V(2).Info("Internal key secret not found, skipping cert publish", "secret", gateway.Spec.App.Otk.InternalKeySecret) + return nil + } + return err + } - switch gateway.Spec.App.Otk.Type { - case securityv1.OtkTypeDMZ: - internalSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalOtkGatewayReference+"-otk-internal-certificates") - sha1Sum = internalSecret.ObjectMeta.Annotations["checksum/data"] + // Check if key was updated by comparing annotation + annotation := "security.brcmlabs.com/otk-internal-key" + currentSha1Sum := "" + if !gateway.Spec.App.Management.Database.Enabled { + // Ephemeral gateway - check pod annotations + podList, err := getGatewayPods(ctx, params) + if err != nil { + return err + } + for _, pod := range podList.Items { + if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { + currentSha1Sum = val + break + } + } + } else { + // DB-backed gateway - check deployment annotations + gatewayDeployment, err := getGatewayDeployment(ctx, params) if err != nil { return err } - annotation = "security.brcmlabs.com/" + gateway.Name + "-" + string(gateway.Spec.App.Otk.Type) + "-certificates" - for k, v := range internalSecret.Data { - bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: k, - CertBase64: base64.StdEncoding.EncodeToString(v), - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, + currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] + } + + // Calculate current key checksum + certData := internalKeySecret.Data["tls.crt"] + keyData := internalKeySecret.Data["tls.key"] + if len(certData) == 0 || len(keyData) == 0 { + return fmt.Errorf("Internal key secret must contain tls.crt and tls.key") + } + + keySecretMap := []struct { + Name string + Crt string + Key string + Alias string + UsageType string + }{ + { + Name: "internal-key", + Crt: string(certData), + Key: string(keyData), + Alias: "otk-internal-key", + UsageType: "SSL", + }, + } + + dataBytes, _ := json.Marshal(&keySecretMap) + h := sha1.New() + h.Write(dataBytes) + newSha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // Only publish if key was updated + if currentSha1Sum == newSha1Sum { + params.Log.V(2).Info("Internal key not updated, skipping cert publish", "gateway", gateway.Name) + return nil + } + + // Publish Internal cert to DMZ (handles ephemeral, DB-backed, and external gateways) + return publishInternalCertToDmz(ctx, params, gateway, internalKeySecret) +} + +func publishDmzCertToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret) error { + // Get Internal gateway + internalGateway := &securityv1.Gateway{} + err := params.Client.Get(ctx, types.NamespacedName{ + Name: gateway.Spec.App.Otk.InternalOtkGatewayReference, + Namespace: gateway.Namespace, + }, internalGateway) + + isExternalGateway := false + if err != nil { + if k8serrors.IsNotFound(err) { + // Gateway not found - check if it's external (port specified) + if gateway.Spec.App.Otk.InternalGatewayPort != 0 { + params.Log.V(2).Info("Internal gateway not found but port specified, treating as external", + "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, + "port", gateway.Spec.App.Otk.InternalGatewayPort) + isExternalGateway = true + } else { + params.Log.V(2).Info("Internal gateway not found and no port specified, skipping cert publish", + "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference) + return nil + } + } else { + return err + } + } + + certData := dmzKeySecret.Data["tls.crt"] + if len(certData) == 0 { + return fmt.Errorf("DMZ key secret must contain tls.crt") + } + + // Parse certificate + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format") + } + + bundle := graphman.Bundle{} + + // Add to TrustedCerts + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ + Name: crtX509.Subject.CommonName, + CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + TrustAnchor: true, + VerifyHostname: false, + RevocationCheckPolicyType: "USE_DEFAULT", + TrustedFor: []graphman.TrustedForType{ + "SSL", + "SIGNING_SERVER_CERTS", + }, + }) + + // Add to FIP Users + bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ + Name: crtX509.Subject.CommonName, + ProviderName: "otk-fips-provider", + SubjectDn: "cn=" + crtX509.Subject.CommonName, + CertBase64: base64.RawStdEncoding.EncodeToString(crtX509.Raw), + }) + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return err + } + + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // If gateway is external (not managed by operator), use specified port and auth secret + if isExternalGateway { + // Parse certificates to extract information for FIP user creation + var certInfo []struct { + commonName string + subjectDn string + certRaw []byte + } + + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + // Extract full Subject DN from certificate + subjectDn := extractSubjectDN(crtX509) + + certInfo = append(certInfo, struct { + commonName string + subjectDn string + certRaw []byte + }{ + commonName: crtX509.Subject.CommonName, + subjectDn: subjectDn, + certRaw: crtX509.Raw, }) } - case securityv1.OtkTypeInternal: - dmzSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzOtkGatewayReference+"-otk-dmz-certificates") - sha1Sum = dmzSecret.ObjectMeta.Annotations["checksum/data"] + return syncDmzCertToExternalInternalGateway(ctx, params, gateway, dmzKeySecret, certInfo) + } + + // Get Internal gateway secret + name := internalGateway.Name + if internalGateway.Spec.App.Management.SecretName != "" { + name = internalGateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) + if err != nil { + return err + } + + annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" + + internalParams := params + internalParams.Instance = internalGateway + + // Note: InternalGatewayPort is used when the gateway is external (not found in cluster) + // For operator-managed gateways, the gateway's own graphman port configuration is used + + if !internalGateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, internalParams) if err != nil { return err } - annotation = "security.brcmlabs.com/" + gateway.Name + "-" + string(gateway.Spec.App.Otk.Type) + "-fips-users" - for k, v := range dmzSecret.Data { - bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ - Name: k, - ProviderName: "otk-fips-provider", - SubjectDn: "cn=" + k, - CertBase64: base64.RawStdEncoding.EncodeToString(v), - }) + err = ReconcileEphemeralGateway(ctx, internalParams, "otk certificates", *podList, internalGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) + if err != nil { + return err + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, internalParams) + if err != nil { + return err + } + err = ReconcileDBGateway(ctx, internalParams, "otk certificates", *gatewayDeployment, internalGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) + if err != nil { + return err } } + return nil +} + +func publishInternalCertToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway, internalKeySecret *corev1.Secret) error { + // Get DMZ gateway + dmzGateway := &securityv1.Gateway{} + err := params.Client.Get(ctx, types.NamespacedName{ + Name: gateway.Spec.App.Otk.DmzOtkGatewayReference, + Namespace: gateway.Namespace, + }, dmzGateway) + + isExternalGateway := false + if err != nil { + if k8serrors.IsNotFound(err) { + // Gateway not found - check if it's external (port specified) + if gateway.Spec.App.Otk.DmzGatewayPort != 0 { + params.Log.V(2).Info("DMZ gateway not found but port specified, treating as external", + "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, + "port", gateway.Spec.App.Otk.DmzGatewayPort) + isExternalGateway = true + } else { + params.Log.V(2).Info("DMZ gateway not found and no port specified, skipping cert publish", + "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference) + return nil + } + } else { + return err + } + } + + certData := internalKeySecret.Data["tls.crt"] + if len(certData) == 0 { + return fmt.Errorf("Internal key secret must contain tls.crt") + } + + // Parse certificate + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + if len(crtStrings) == 0 { + return fmt.Errorf("invalid certificate format") + } + + bundle := graphman.Bundle{} + + // Add to TrustedCerts + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ + Name: crtX509.Subject.CommonName, + CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + TrustAnchor: true, + VerifyHostname: false, + RevocationCheckPolicyType: "USE_DEFAULT", + TrustedFor: []graphman.TrustedForType{ + "SSL", + "SIGNING_SERVER_CERTS", + }, + }) + } + bundleBytes, err := json.Marshal(bundle) if err != nil { return err } - name := gateway.Name - if gateway.Spec.App.Management.SecretName != "" { - name = gateway.Spec.App.Management.SecretName + // Calculate checksum + h := sha1.New() + h.Write(bundleBytes) + sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) + + // If gateway is external (not managed by operator), use specified port and auth secret + if isExternalGateway { + return syncInternalCertToExternalDmzGateway(ctx, params, gateway, bundleBytes, sha1Sum) } - gwSecret, err := getGatewaySecret(ctx, params, name) + // Get DMZ gateway secret + name := dmzGateway.Name + if dmzGateway.Spec.App.Management.SecretName != "" { + name = dmzGateway.Spec.App.Management.SecretName + } + gwSecret, err := getGatewaySecret(ctx, params, name) if err != nil { return err } - if !gateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, params) + annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" + + dmzParams := params + dmzParams.Instance = dmzGateway + + // Note: DmzGatewayPort is used when the gateway is external (not found in cluster) + // For operator-managed gateways, the gateway's own graphman port configuration is used + + if !dmzGateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, dmzParams) if err != nil { return err } - err = ReconcileEphemeralGateway(ctx, params, "otk certificates", *podList, gateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) + err = ReconcileEphemeralGateway(ctx, dmzParams, "otk certificates", *podList, dmzGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) if err != nil { return err } } else { - gatewayDeployment, err := getGatewayDeployment(ctx, params) + gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) if err != nil { return err } - err = ReconcileDBGateway(ctx, params, "otk certificates", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) + err = ReconcileDBGateway(ctx, dmzParams, "otk certificates", *gatewayDeployment, dmzGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) if err != nil { return err } @@ -138,3 +523,261 @@ func applyOtkCertificates(ctx context.Context, params Params, gateway *securityv return nil } + +// syncDmzCertToExternalInternalGateway syncs DMZ certificate to an external Internal gateway +// using graphman. First it adds the certificate as a trusted cert, then creates a FIP user. +func syncDmzCertToExternalInternalGateway(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret, certInfo []struct { + commonName string + subjectDn string + certRaw []byte +}) error { + // Get auth secret for external Internal gateway + if gateway.Spec.App.Otk.InternalAuthSecret == "" { + return fmt.Errorf("internalAuthSecret is required for external Internal gateway") + } + + authSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalAuthSecret) + if err != nil { + return fmt.Errorf("failed to get auth secret for external Internal gateway: %w", err) + } + + // Parse username and password from auth secret + username, password := parseGatewaySecret(authSecret) + if username == "" || password == "" { + return fmt.Errorf("could not retrieve gateway credentials from auth secret: %s", gateway.Spec.App.Otk.InternalAuthSecret) + } + + // Build endpoint URL for external gateway + // Format: :/graphman + // ApplyGraphmanBundle expects format: host:port/path (without https://) + gatewayReference := gateway.Spec.App.Otk.InternalOtkGatewayReference + port := gateway.Spec.App.Otk.InternalGatewayPort + if port == 0 { + port = 9443 // Default graphman port + } + + // For external gateways, the reference might be a hostname or IP + // If it's just a name without domain, we might need to construct a full hostname + // For now, use the reference as-is (could be FQDN, hostname, or IP) + endpoint := fmt.Sprintf("%s:%d/graphman", gatewayReference, port) + + // Step 1: Sync DMZ certificate as TrustedCert first + certData := dmzKeySecret.Data["tls.crt"] + if len(certData) == 0 { + return fmt.Errorf("DMZ key secret must contain tls.crt") + } + + crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") + trustedCertBundle := graphman.Bundle{} + + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + + trustedCertBundle.TrustedCerts = append(trustedCertBundle.TrustedCerts, &graphman.TrustedCertInput{ + Name: crtX509.Subject.CommonName, + CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + TrustAnchor: true, + VerifyHostname: false, + RevocationCheckPolicyType: "USE_DEFAULT", + TrustedFor: []graphman.TrustedForType{ + "SSL", + "SIGNING_SERVER_CERTS", + }, + }) + } + + trustedCertBundleBytes, err := json.Marshal(trustedCertBundle) + if err != nil { + return fmt.Errorf("failed to marshal trusted cert bundle: %w", err) + } + + params.Log.V(2).Info("Syncing DMZ certificate as TrustedCert to external Internal gateway", + "gateway", gatewayReference, + "endpoint", endpoint) + + // Apply trusted cert bundle first + err = util.ApplyGraphmanBundle(username, password, endpoint, "", trustedCertBundleBytes) + if err != nil { + return fmt.Errorf("failed to sync DMZ certificate as TrustedCert to external Internal gateway: %w", err) + } + + params.Log.Info("Successfully synced DMZ certificate as TrustedCert to external Internal gateway", + "gateway", gatewayReference, + "endpoint", endpoint) + + // Step 2: Create FIP user with DMZ certificate + if len(certInfo) == 0 { + params.Log.V(2).Info("No certificate info available, skipping FIP user creation") + return nil + } + + fipUserBundle := graphman.Bundle{} + + for _, info := range certInfo { + // Use the extracted Subject DN (not just "cn=" + CommonName) + // Since FIP identity provider doesn't have a default subject dn, we must provide it + fipUserBundle.FipUsers = append(fipUserBundle.FipUsers, &graphman.FipUserInput{ + Name: info.commonName, + ProviderName: "otk-fips-provider", + SubjectDn: info.subjectDn, // Full Subject DN from certificate + CertBase64: base64.RawStdEncoding.EncodeToString(info.certRaw), + }) + } + + fipUserBundleBytes, err := json.Marshal(fipUserBundle) + if err != nil { + return fmt.Errorf("failed to marshal FIP user bundle: %w", err) + } + + params.Log.V(2).Info("Creating FIP user with DMZ certificate in external Internal gateway", + "gateway", gatewayReference, + "endpoint", endpoint) + + // Apply FIP user bundle after certificate is synced + err = util.ApplyGraphmanBundle(username, password, endpoint, "", fipUserBundleBytes) + if err != nil { + return fmt.Errorf("failed to create FIP user with DMZ certificate in external Internal gateway: %w", err) + } + + params.Log.Info("Successfully created FIP user with DMZ certificate in external Internal gateway", + "gateway", gatewayReference, + "endpoint", endpoint) + + return nil +} + +// extractSubjectDN extracts the full Subject DN from an x509 certificate +// Format: CN=name,OU=org unit,O=org,C=country, etc. +func extractSubjectDN(cert *x509.Certificate) string { + var parts []string + + // Add CommonName + if cert.Subject.CommonName != "" { + parts = append(parts, "CN="+cert.Subject.CommonName) + } + + // Add Country + for _, c := range cert.Subject.Country { + if c != "" { + parts = append(parts, "C="+c) + } + } + + // Add Organization + for _, o := range cert.Subject.Organization { + if o != "" { + parts = append(parts, "O="+o) + } + } + + // Add Organizational Unit + for _, ou := range cert.Subject.OrganizationalUnit { + if ou != "" { + parts = append(parts, "OU="+ou) + } + } + + // Add Locality + for _, l := range cert.Subject.Locality { + if l != "" { + parts = append(parts, "L="+l) + } + } + + // Add Province/State + for _, p := range cert.Subject.Province { + if p != "" { + parts = append(parts, "ST="+p) + } + } + + // Add Street Address + for _, s := range cert.Subject.StreetAddress { + if s != "" { + parts = append(parts, "STREET="+s) + } + } + + // Add Postal Code + for _, pc := range cert.Subject.PostalCode { + if pc != "" { + parts = append(parts, "POSTALCODE="+pc) + } + } + + // Add Serial Number + if cert.Subject.SerialNumber != "" { + parts = append(parts, "SERIALNUMBER="+cert.Subject.SerialNumber) + } + + // Join all parts with comma + if len(parts) == 0 { + // Fallback to CN if nothing else is available + if cert.Subject.CommonName != "" { + return "CN=" + cert.Subject.CommonName + } + return "" + } + + return strings.Join(parts, ",") +} + +// syncInternalCertToExternalDmzGateway syncs Internal certificate to an external DMZ gateway +// using graphman. It adds the certificate as a trusted cert. +func syncInternalCertToExternalDmzGateway(ctx context.Context, params Params, gateway *securityv1.Gateway, bundleBytes []byte, sha1Sum string) error { + // Get auth secret for external DMZ gateway + if gateway.Spec.App.Otk.DmzAuthSecret == "" { + return fmt.Errorf("dmzAuthSecret is required for external DMZ gateway") + } + + authSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzAuthSecret) + if err != nil { + return fmt.Errorf("failed to get auth secret for external DMZ gateway: %w", err) + } + + // Parse username and password from auth secret + username, password := parseGatewaySecret(authSecret) + if username == "" || password == "" { + return fmt.Errorf("could not retrieve gateway credentials from auth secret: %s", gateway.Spec.App.Otk.DmzAuthSecret) + } + + // Build endpoint URL for external gateway + // Format: :/graphman + // ApplyGraphmanBundle expects format: host:port/path (without https://) + gatewayReference := gateway.Spec.App.Otk.DmzOtkGatewayReference + port := gateway.Spec.App.Otk.DmzGatewayPort + if port == 0 { + port = 9443 // Default graphman port + } + + // For external gateways, the reference might be a hostname or IP + endpoint := fmt.Sprintf("%s:%d/graphman", gatewayReference, port) + + params.Log.V(2).Info("Syncing Internal certificate to external DMZ gateway", + "gateway", gatewayReference, + "endpoint", endpoint, + "sha1Sum", sha1Sum) + + // Apply bundle to external gateway using graphman + err = util.ApplyGraphmanBundle(username, password, endpoint, "", bundleBytes) + if err != nil { + return fmt.Errorf("failed to sync Internal certificate to external DMZ gateway: %w", err) + } + + params.Log.Info("Successfully synced Internal certificate to external DMZ gateway", + "gateway", gatewayReference, + "endpoint", endpoint, + "sha1Sum", sha1Sum) + + return nil +} From 089de635c3e38dfcc378b85bf1c5cc8882c69b92 Mon Sep 17 00:00:00 2001 From: as673366 Date: Tue, 23 Dec 2025 12:55:48 +0530 Subject: [PATCH 04/11] adding readme with in depth details --- .../gateway/otk/README-DUAL-GATEWAY-OTK.md | 498 ++++++++++++++++++ pkg/gateway/reconcile/externalkeys.go | 19 +- pkg/gateway/reconcile/l7otkcertificates.go | 181 ++++++- 3 files changed, 682 insertions(+), 16 deletions(-) create mode 100644 example/gateway/otk/README-DUAL-GATEWAY-OTK.md diff --git a/example/gateway/otk/README-DUAL-GATEWAY-OTK.md b/example/gateway/otk/README-DUAL-GATEWAY-OTK.md new file mode 100644 index 00000000..7e42ea61 --- /dev/null +++ b/example/gateway/otk/README-DUAL-GATEWAY-OTK.md @@ -0,0 +1,498 @@ +# Dual Gateway OTK Configuration Guide + +This guide describes how to configure a Dual Gateway OAuth Toolkit (OTK) deployment using the Layer7 Gateway Operator. In a dual gateway setup, one gateway acts as the DMZ (Demilitarized Zone) gateway and another acts as the Internal gateway, providing enhanced security and separation of concerns. + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [Configuration Overview](#configuration-overview) +- [Step 1: Create Required Secrets](#step-1-create-required-secrets) +- [Step 2: Configure DMZ Gateway](#step-2-configure-dmz-gateway) +- [Step 3: Configure Internal Gateway](#step-3-configure-internal-gateway) +- [Key Configuration Fields](#key-configuration-fields) +- [Deployment](#deployment) +- [Certificate Synchronization](#certificate-synchronization) +- [External Gateway Support](#external-gateway-support) +- [Troubleshooting](#troubleshooting) + +## Overview + +The Dual Gateway OTK deployment consists of: + +- **DMZ Gateway**: Handles external client requests and acts as the OAuth authorization server +- **Internal Gateway**: Handles token validation and resource server operations + +The operator automatically synchronizes certificates and keys between the two gateways, ensuring secure communication and proper OAuth flow. + +## Configuration Overview + +The dual gateway setup requires: + +1. **TLS Secrets**: For DMZ and Internal gateway keys/certificates +2. **Auth Secrets**: For gateway authentication credentials +3. **DMZ Gateway Configuration**: With `type: dmz` +4. **Internal Gateway Configuration**: With `type: internal` + +## Step 1: Create Required Secrets + +### Create TLS Secrets + +You need to create TLS secrets for both DMZ and Internal gateways. These secrets must be of type `kubernetes.io/tls` and contain: +- `tls.crt`: The certificate +- `tls.key`: The private key + +#### Option 1: Using the Provided Script + +A helper script is available to generate self-signed certificates and create all required secrets: + +```bash +cd example/gateway/otk/secrets +./create-secrets.sh +``` + +This script creates: +- `otk-dmz-tls-secret`: TLS secret for DMZ gateway +- `otk-internal-tls-secret`: TLS secret for Internal gateway +- `otk-dmz-auth-secret`: Authentication secret for DMZ gateway (username: `admin`, password: `7layer`) +- `otk-internal-auth-secret`: Authentication secret for Internal gateway (username: `admin`, password: `7layer`) + +#### Option 2: Manual Secret Creation + +**DMZ TLS Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-dmz-tls-secret + namespace: default +type: kubernetes.io/tls +data: + tls.crt: + tls.key: +``` + +**Internal TLS Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-internal-tls-secret + namespace: default +type: kubernetes.io/tls +data: + tls.crt: + tls.key: +``` + +**DMZ Auth Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-dmz-auth-secret + namespace: default +type: Opaque +stringData: + SSG_ADMIN_USERNAME: admin + SSG_ADMIN_PASSWORD: 7layer +``` + +**Internal Auth Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-internal-auth-secret + namespace: default +type: Opaque +stringData: + SSG_ADMIN_USERNAME: admin + SSG_ADMIN_PASSWORD: 7layer +``` + +## Step 2: Configure DMZ Gateway + +The DMZ gateway configuration should include: + +- `otk.type: dmz` +- Reference to Internal gateway +- DMZ key secret reference +- Internal auth secret for communication with Internal gateway +- Database configuration + +### Sample DMZ Gateway Configuration + +```yaml +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-dmz +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-dmz-tls-secret + enabled: true + alias: otk-dmz-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: dmz + # Reference to Internal gateway (can be Gateway name or external hostname) + internalGatewayReference: otk-ssg-internal + # InternalGatewayPort is used when the Internal gateway is external + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + internalGatewayPort: 9443 + # SyncIntervalSeconds determines how often certificates are synchronized + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for DMZ key + dmzKeySecret: otk-dmz-tls-secret + # Auth secret for Internal gateway communication + internalAuthSecret: otk-internal-auth-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + gateway: + username: otk_user + password: otkUserPass + readOnly: + username: readonly_user + password: readonly_userPass + admin: + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + management: + secretName: gateway-secret + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + dynamicSyncPort: 9443 + cluster: + hostname: gateway.brcmlabs.com + service: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP +``` + +## Step 3: Configure Internal Gateway + +The Internal gateway configuration should include: + +- `otk.type: internal` +- Reference to DMZ gateway +- Internal key secret reference +- DMZ auth secret for communication with DMZ gateway +- Database configuration (shared with DMZ) + +### Sample Internal Gateway Configuration + +```yaml +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-internal +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-internal-tls-secret + enabled: true + alias: otk-internal-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: internal + # Reference to DMZ gateway (can be Gateway name or external hostname) + dmzGatewayReference: otk-ssg-dmz + # DmzGatewayPort is used when the DMZ gateway is external + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + dmzGatewayPort: 9443 + # SyncIntervalSeconds determines how often certificates are synchronized + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for Internal key + internalKeySecret: otk-internal-tls-secret + # Auth secret for DMZ gateway communication + dmzAuthSecret: otk-dmz-auth-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + gateway: + username: otk_user + password: otkUserPass + readOnly: + username: readonly_user + password: readonly_userPass + admin: + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + management: + secretName: gateway-secret + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + cluster: + hostname: gateway.brcmlabs.com + service: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP + - name: management + port: 9443 + targetPort: 9443 + protocol: TCP +``` + +## Key Configuration Fields + +### OTK-Specific Fields + +| Field | Description | Required | Default | +|-------|-------------|----------|---------| +| `otk.enabled` | Enable OTK installation | Yes | `false` | +| `otk.type` | OTK type: `dmz`, `internal`, or `single` | Yes | - | +| `otk.initContainerImage` | OTK init container image | Yes | - | +| `otk.dmzKeySecret` | Reference to TLS secret containing DMZ key/cert | Yes (DMZ) | - | +| `otk.internalKeySecret` | Reference to TLS secret containing Internal key/cert | Yes (Internal) | - | +| `otk.dmzAuthSecret` | Reference to secret with DMZ gateway credentials | Yes (Internal) | - | +| `otk.internalAuthSecret` | Reference to secret with Internal gateway credentials | Yes (DMZ) | - | +| `otk.dmzGatewayReference` | Reference to DMZ gateway (name or hostname) | Yes (Internal) | - | +| `otk.internalGatewayReference` | Reference to Internal gateway (name or hostname) | Yes (DMZ) | - | +| `otk.dmzGatewayPort` | Port for DMZ gateway (when external) | No | `9443` or `graphmanDynamicSync` port | +| `otk.internalGatewayPort` | Port for Internal gateway (when external) | No | `9443` or `graphmanDynamicSync` port | +| `otk.syncIntervalSeconds` | Certificate sync interval in seconds | No | `RuntimeSyncIntervalSeconds` or `10` | +| `otk.port` | OTK port (defaults to 8443) | No | `8443` | + +### External Keys Configuration + +Both gateways must have `externalKeys` configured with the `otk: true` flag: + +```yaml +externalKeys: +- name: otk-dmz-tls-secret # or otk-internal-tls-secret + enabled: true + alias: otk-dmz-key # or otk-internal-key + keyUsageType: SSL + otk: true # Required for OTK key handling +``` + +## Deployment + +### 1. Create Secrets + +```bash +cd example/gateway/otk/secrets +./create-secrets.sh default +``` + +### 2. Deploy DMZ Gateway + +```bash +kubectl apply -f example/gateway/otk/otk-ssg-dmz.yaml +``` + +### 3. Deploy Internal Gateway + +```bash +kubectl apply -f example/gateway/otk/otk-ssg-internal.yaml +``` + +### 4. Verify Deployment + +```bash +# Check gateway pods +kubectl get pods -l app=gateway + +# Check gateway status +kubectl get gateway otk-ssg-dmz +kubectl get gateway otk-ssg-internal + +# Check logs +kubectl logs -l app=gateway,gateway-name=otk-ssg-dmz +kubectl logs -l app=gateway,gateway-name=otk-ssg-internal +``` + +## Certificate Synchronization + +The operator automatically synchronizes certificates between DMZ and Internal gateways: + +1. **DMZ Certificate → Internal Gateway**: When the DMZ certificate is updated, it's automatically published to the Internal gateway as a trusted certificate and used for FIP (Federated Identity Provider) user creation. + +2. **Internal Certificate → DMZ Gateway**: When the Internal certificate is updated, it's automatically published to the DMZ gateway as a trusted certificate. + +3. **Sync Interval**: Controlled by `syncIntervalSeconds` (default: 10 seconds or `RuntimeSyncIntervalSeconds`). + +4. **Key Updates**: When DMZ or Internal keys are updated: + - The key is synchronized to the respective gateway + - The DMZ private key name is updated in the cluster-wide property `otk.dmz.private_key.name` (DMZ gateway only) + - Old certificates are removed before new ones are published + +## External Gateway Support + +The operator supports scenarios where one or both gateways are external (not managed by the operator): + +### External DMZ Gateway + +If the DMZ gateway is external, configure the Internal gateway with: + +```yaml +otk: + type: internal + dmzGatewayReference: external-dmz-gateway.example.com + dmzGatewayPort: 9443 # Port for Graphman API + dmzAuthSecret: otk-dmz-auth-secret +``` + +### External Internal Gateway + +If the Internal gateway is external, configure the DMZ gateway with: + +```yaml +otk: + type: dmz + internalGatewayReference: external-internal-gateway.example.com + internalGatewayPort: 9443 # Port for Graphman API + internalAuthSecret: otk-internal-auth-secret +``` + +### External Gateway Requirements + +- Graphman API must be enabled and accessible +- Authentication credentials must be provided via auth secrets +- The correct port must be specified if different from default (9443) +- The gateway must be reachable from the operator's network + +## Troubleshooting + +### Common Issues + +1. **Certificates not synchronizing** + - Verify `syncIntervalSeconds` is set appropriately + - Check that Graphman is enabled on both gateways + - Verify auth secrets are correctly configured + - Check operator logs for errors + +2. **Gateway communication failures** + - Verify gateway references are correct (name or hostname) + - Check network connectivity between gateways + - Verify ports are correctly configured + - Ensure auth secrets contain valid credentials + +3. **Key update failures** + - Verify TLS secrets are of type `kubernetes.io/tls` + - Check that secrets contain both `tls.crt` and `tls.key` + - Ensure `externalKeys` have `otk: true` flag + - Verify alias matches the expected value + +4. **Database connection issues** + - Verify database credentials in `otk.database.auth` + - Check JDBC URL is correct and accessible + - Ensure database exists or `create: true` is set + - Verify database wait timeout is sufficient + +### Checking Certificate Sync Status + +```bash +# Check annotations for certificate thumbprints +kubectl get gateway otk-ssg-dmz -o jsonpath='{.metadata.annotations}' +kubectl get gateway otk-ssg-internal -o jsonpath='{.metadata.annotations}' + +# Check cluster-wide properties +kubectl exec -it -- /opt/SecureSpan/Gateway/node/default/bin/ssgconfig \ + get cluster-wide-properties | grep otk.dmz.private_key.name +``` + +### Operator Logs + +```bash +# View operator logs +kubectl logs -n -l control-plane=controller-manager + +# Filter for OTK-related logs +kubectl logs -n -l control-plane=controller-manager | grep -i otk +``` + +## Additional Resources + +- [Layer7 Gateway Operator Documentation](https://github.com/broadcom/layer7-operator) +- [OAuth Toolkit Documentation](https://techdocs.broadcom.com/us/en/ca-enterprise-software/layer7-api-management/api-gateway/11-1.html) +- Example configurations: `example/gateway/otk/` + +--- + +**Note**: This configuration guide assumes you have a working Kubernetes cluster with the Layer7 Gateway Operator installed. Adjust namespaces, hostnames, and other values according to your environment. + diff --git a/pkg/gateway/reconcile/externalkeys.go b/pkg/gateway/reconcile/externalkeys.go index 8a76eca1..a19e3300 100644 --- a/pkg/gateway/reconcile/externalkeys.go +++ b/pkg/gateway/reconcile/externalkeys.go @@ -228,7 +228,7 @@ func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Ga Crt: string(certData), Key: string(keyData), Alias: "otk-dmz-key", - UsageType: "SSL", + UsageType: "", }, } @@ -257,13 +257,13 @@ func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Ga // Check if DMZ key was already applied (to determine if update is needed) keyWasUpdated := false + currentSha1Sum := "" if !gateway.Spec.App.Management.Database.Enabled { podList, err := getGatewayPods(ctx, params) if err != nil { return err } // Check current annotation value before update - currentSha1Sum := "" for _, pod := range podList.Items { if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { currentSha1Sum = val @@ -275,29 +275,30 @@ func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Ga return err } // Key was updated if sha1Sum changed - keyWasUpdated = (currentSha1Sum != sha1Sum) + keyWasUpdated = currentSha1Sum != sha1Sum } else { gatewayDeployment, err := getGatewayDeployment(ctx, params) if err != nil { return err } // Check current annotation value before update - currentSha1Sum := gatewayDeployment.ObjectMeta.Annotations[annotation] + currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] err = ReconcileDBGateway(ctx, params, "otk dmz key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) if err != nil { return err } // Key was updated if sha1Sum changed (ReconcileDBGateway returns early if already applied) - keyWasUpdated = (currentSha1Sum != sha1Sum) + keyWasUpdated = currentSha1Sum != sha1Sum } - // Update cluster property only if DMZ key was updated - if keyWasUpdated { + // Update cluster property if DMZ key was updated OR if this is the first reconciliation (currentSha1Sum is empty) + // This ensures the CWP is set on first reconciliation, not just on key updates + if keyWasUpdated || currentSha1Sum == "" { if err := updateDmzPrivateKeyClusterProperty(ctx, params, gateway, "otk-dmz-key"); err != nil { params.Log.V(2).Info("Failed to update DMZ private key cluster property", "error", err, "gateway", gateway.Name) // Don't fail the entire operation if cluster property update fails } - } else if !keyWasUpdated { + } else { params.Log.V(2).Info("DMZ key was not updated, skipping cluster property update", "gateway", gateway.Name) } @@ -340,7 +341,7 @@ func updateInternalWithKey(ctx context.Context, params Params, gateway *security Crt: string(certData), Key: string(keyData), Alias: "otk-internal-key", - UsageType: "SSL", + UsageType: "", }, } diff --git a/pkg/gateway/reconcile/l7otkcertificates.go b/pkg/gateway/reconcile/l7otkcertificates.go index d8703d70..04de4470 100644 --- a/pkg/gateway/reconcile/l7otkcertificates.go +++ b/pkg/gateway/reconcile/l7otkcertificates.go @@ -26,10 +26,12 @@ package reconcile import ( + "bytes" "context" "crypto/sha1" "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/json" "encoding/pem" "fmt" @@ -136,7 +138,7 @@ func publishDmzCertificatesToInternal(ctx context.Context, params Params, gatewa Crt: string(certData), Key: string(keyData), Alias: "otk-dmz-key", - UsageType: "SSL", + UsageType: "", }, } @@ -217,7 +219,7 @@ func publishInternalCertificatesToDmz(ctx context.Context, params Params, gatewa Crt: string(certData), Key: string(keyData), Alias: "otk-internal-key", - UsageType: "SSL", + UsageType: "", }, } @@ -274,9 +276,82 @@ func publishDmzCertToInternal(ctx context.Context, params Params, gateway *secur return fmt.Errorf("invalid certificate format") } + // Before adding new certs, remove existing ones if they were previously applied + // Check if certs were previously applied by checking the annotation + annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" + thumbprintAnnotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates-thumbprints" + previousCertChecksum := "" + var oldThumbprints []string + if !isExternalGateway { + if !internalGateway.Spec.App.Management.Database.Enabled { + podList, err := getGatewayPods(ctx, params) + if err == nil { + for _, pod := range podList.Items { + if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { + previousCertChecksum = val + } + if val, ok := pod.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { + // Parse comma-separated thumbprints + oldThumbprints = strings.Split(val, ",") + } + if previousCertChecksum != "" { + break + } + } + } + } else { + gatewayDeployment, err := getGatewayDeployment(ctx, params) + if err == nil { + previousCertChecksum = gatewayDeployment.ObjectMeta.Annotations[annotation] + if val, ok := gatewayDeployment.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { + oldThumbprints = strings.Split(val, ",") + } + } + } + } + bundle := graphman.Bundle{} - // Add to TrustedCerts + // If we have old thumbprints, add deletion mappings before adding new certs + if len(oldThumbprints) > 0 && previousCertChecksum != "" { + if bundle.Properties == nil { + bundle.Properties = &graphman.BundleProperties{ + Mappings: graphman.BundleMappings{}, + } + } + for _, thumbprint := range oldThumbprints { + thumbprint = strings.TrimSpace(thumbprint) + if thumbprint != "" { + bundle.Properties.Mappings.TrustedCerts = append(bundle.Properties.Mappings.TrustedCerts, &graphman.MappingInstructionInput{ + Action: graphman.MappingActionDelete, + Source: graphman.MappingSource{ThumbprintSha1: thumbprint}, + }) + } + } + // Also remove old FIP users with the same names + // We'll identify them by the cert CommonName pattern + for _, certStr := range crtStrings { + if certStr == "" { + continue + } + b, _ := pem.Decode([]byte(certStr)) + if b == nil { + continue + } + crtX509, err := x509.ParseCertificate(b.Bytes) + if err != nil { + continue + } + // Remove FIP user by name (CommonName) + bundle.Properties.Mappings.FipUsers = append(bundle.Properties.Mappings.FipUsers, &graphman.MappingInstructionInput{ + Action: graphman.MappingActionDelete, + Source: graphman.MappingSource{Name: crtX509.Subject.CommonName}, + }) + } + } + + // Calculate thumbprints for new certs and add to TrustedCerts + var newThumbprints []string for _, certStr := range crtStrings { if certStr == "" { continue @@ -290,9 +365,19 @@ func publishDmzCertToInternal(ctx context.Context, params Params, gateway *secur continue } + // Calculate thumbprint for this cert + thumbprint, err := calculateCertThumbprint(crtX509.Raw) + if err != nil { + params.Log.V(2).Info("Failed to calculate cert thumbprint", "error", err, "cert", crtX509.Subject.CommonName) + thumbprint = "" // Continue without thumbprint + } else { + newThumbprints = append(newThumbprints, thumbprint) + } + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ Name: crtX509.Subject.CommonName, CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + ThumbprintSha1: thumbprint, TrustAnchor: true, VerifyHostname: false, RevocationCheckPolicyType: "USE_DEFAULT", @@ -370,8 +455,6 @@ func publishDmzCertToInternal(ctx context.Context, params Params, gateway *secur return err } - annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" - internalParams := params internalParams.Instance = internalGateway @@ -439,9 +522,66 @@ func publishInternalCertToDmz(ctx context.Context, params Params, gateway *secur return fmt.Errorf("invalid certificate format") } + // Before adding new certs, remove existing ones if they were previously applied + // Check if certs were previously applied by checking the annotation + annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" + thumbprintAnnotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates-thumbprints" + previousCertChecksum := "" + var oldThumbprints []string + if !isExternalGateway { + if !dmzGateway.Spec.App.Management.Database.Enabled { + dmzParams := params + dmzParams.Instance = dmzGateway + podList, err := getGatewayPods(ctx, dmzParams) + if err == nil { + for _, pod := range podList.Items { + if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { + previousCertChecksum = val + } + if val, ok := pod.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { + // Parse comma-separated thumbprints + oldThumbprints = strings.Split(val, ",") + } + if previousCertChecksum != "" { + break + } + } + } + } else { + dmzParams := params + dmzParams.Instance = dmzGateway + gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) + if err == nil { + previousCertChecksum = gatewayDeployment.ObjectMeta.Annotations[annotation] + if val, ok := gatewayDeployment.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { + oldThumbprints = strings.Split(val, ",") + } + } + } + } + bundle := graphman.Bundle{} - // Add to TrustedCerts + // If we have old thumbprints, add deletion mappings before adding new certs + if len(oldThumbprints) > 0 && previousCertChecksum != "" { + if bundle.Properties == nil { + bundle.Properties = &graphman.BundleProperties{ + Mappings: graphman.BundleMappings{}, + } + } + for _, thumbprint := range oldThumbprints { + thumbprint = strings.TrimSpace(thumbprint) + if thumbprint != "" { + bundle.Properties.Mappings.TrustedCerts = append(bundle.Properties.Mappings.TrustedCerts, &graphman.MappingInstructionInput{ + Action: graphman.MappingActionDelete, + Source: graphman.MappingSource{ThumbprintSha1: thumbprint}, + }) + } + } + } + + // Calculate thumbprints for new certs and add to TrustedCerts + var newThumbprints []string for _, certStr := range crtStrings { if certStr == "" { continue @@ -455,9 +595,19 @@ func publishInternalCertToDmz(ctx context.Context, params Params, gateway *secur continue } + // Calculate thumbprint for this cert + thumbprint, err := calculateCertThumbprint(crtX509.Raw) + if err != nil { + params.Log.V(2).Info("Failed to calculate cert thumbprint", "error", err, "cert", crtX509.Subject.CommonName) + thumbprint = "" // Continue without thumbprint + } else { + newThumbprints = append(newThumbprints, thumbprint) + } + bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ Name: crtX509.Subject.CommonName, CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), + ThumbprintSha1: thumbprint, TrustAnchor: true, VerifyHostname: false, RevocationCheckPolicyType: "USE_DEFAULT", @@ -493,7 +643,8 @@ func publishInternalCertToDmz(ctx context.Context, params Params, gateway *secur return err } - annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" + // annotation is already declared above, reuse it + // annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" dmzParams := params dmzParams.Instance = dmzGateway @@ -781,3 +932,19 @@ func syncInternalCertToExternalDmzGateway(ctx context.Context, params Params, ga return nil } + +// calculateCertThumbprint calculates the SHA1 thumbprint of a certificate in the format expected by Graphman +// Format: base64-encoded hex string of SHA1 fingerprint +func calculateCertThumbprint(rawCert []byte) (string, error) { + fingerprint := sha1.Sum(rawCert) + var buf bytes.Buffer + for _, f := range fingerprint { + fmt.Fprintf(&buf, "%02X", f) + } + hexDump, err := hex.DecodeString(buf.String()) + if err != nil { + return "", err + } + buf.Reset() + return base64.StdEncoding.EncodeToString(hexDump), nil +} From 1846943f776ec17f8e2c0632cdeb7ea7139c22dc Mon Sep 17 00:00:00 2001 From: as673366 Date: Tue, 23 Dec 2025 14:37:47 +0530 Subject: [PATCH 05/11] some additional changes wrt customization repos --- example/gateway/otk/otk-ssg-dmz.yaml | 36 +++++++++++++++++++++-- example/gateway/otk/otk-ssg-internal.yaml | 36 +++++++++++++++++++++-- example/repositories/kustomization.yaml | 2 ++ pkg/util/graphman.go | 10 +++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/example/gateway/otk/otk-ssg-dmz.yaml b/example/gateway/otk/otk-ssg-dmz.yaml index 9bae180e..967b2eed 100644 --- a/example/gateway/otk/otk-ssg-dmz.yaml +++ b/example/gateway/otk/otk-ssg-dmz.yaml @@ -84,8 +84,40 @@ spec: databaseWaitTimeout: 60 autoscaling: enabled: false - bundle: [] - repositoryReferences: [] + bundle: + - type: restman + source: secret + name: restman-bootstrap-bundle + - type: graphman + source: secret + name: graphman-bootstrap-bundle + repositoryReferences: + - name: l7-gw-myframework + enabled: true + type: static + encryption: + existingSecret: graphman-encryption-secret + key: FRAMEWORK_ENCRYPTION_PASSPHRASE + - name: l7-gw-myapis + enabled: true + type: dynamic + encryption: + existingSecret: graphman-encryption-secret + key: APIS_ENCRYPTION_PASSPHRASE + - name: l7-gw-mysubscriptions + enabled: true + type: dynamic + encryption: + existingSecret: graphman-encryption-secret + key: SUBSCRIPTIONS_ENCRYPTION_PASSPHRASE + - name: local-reference-repository + enabled: true + type: dynamic + encryption: { } + - name: otk-customizations-dmz + enabled: true + type: dynamic + encryption: { } bootstrap: script: enabled: true diff --git a/example/gateway/otk/otk-ssg-internal.yaml b/example/gateway/otk/otk-ssg-internal.yaml index 786d60d4..c0cd32dd 100644 --- a/example/gateway/otk/otk-ssg-internal.yaml +++ b/example/gateway/otk/otk-ssg-internal.yaml @@ -78,8 +78,40 @@ spec: databaseWaitTimeout: 60 autoscaling: enabled: false - bundle: [] - repositoryReferences: [] + bundle: + - type: restman + source: secret + name: restman-bootstrap-bundle + - type: graphman + source: secret + name: graphman-bootstrap-bundle + repositoryReferences: + - name: l7-gw-myframework + enabled: true + type: static + encryption: + existingSecret: graphman-encryption-secret + key: FRAMEWORK_ENCRYPTION_PASSPHRASE + - name: l7-gw-myapis + enabled: true + type: dynamic + encryption: + existingSecret: graphman-encryption-secret + key: APIS_ENCRYPTION_PASSPHRASE + - name: l7-gw-mysubscriptions + enabled: true + type: dynamic + encryption: + existingSecret: graphman-encryption-secret + key: SUBSCRIPTIONS_ENCRYPTION_PASSPHRASE + - name: local-reference-repository + enabled: true + type: dynamic + encryption: { } + - name: otk-customizations-internal + enabled: true + type: dynamic + encryption: { } bootstrap: script: enabled: true diff --git a/example/repositories/kustomization.yaml b/example/repositories/kustomization.yaml index 46194a77..834d6c23 100644 --- a/example/repositories/kustomization.yaml +++ b/example/repositories/kustomization.yaml @@ -6,4 +6,6 @@ resources: - ./apis-repository.yaml - ./local-reference-repository.yaml - ./otk-customizations-single.yaml + - ./otk-customizations-dmz.yaml + - ./otk-customizations-internal.yaml - ../base diff --git a/pkg/util/graphman.go b/pkg/util/graphman.go index cabd1049..02e0c799 100644 --- a/pkg/util/graphman.go +++ b/pkg/util/graphman.go @@ -595,6 +595,11 @@ func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int) ([]byt Soap: false, }) } + bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ + Name: "otk.port", + Value: strconv.Itoa(otkPort), + Description: "OTK Port", + }) } bundle.FederatedIdps = append(bundle.FederatedIdps, &graphman.FederatedIdpInput{ @@ -629,6 +634,11 @@ func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int) ([]byt Soap: false, }) } + bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ + Name: "otk.port", + Value: strconv.Itoa(otkPort), + Description: "OTK Port", + }) } case "SINGLE": bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ From bec7421b5bfa60312bf691e63b22bab37058ebca Mon Sep 17 00:00:00 2001 From: as673366 Date: Fri, 23 Jan 2026 12:33:25 +0530 Subject: [PATCH 06/11] adding dmz & internal customization repo & commenting reference as of now --- example/repositories/kustomization.yaml | 4 ++-- example/repositories/otk-customizations-dmz.yaml | 10 ++++++++++ example/repositories/otk-customizations-internal.yaml | 10 ++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 example/repositories/otk-customizations-dmz.yaml create mode 100644 example/repositories/otk-customizations-internal.yaml diff --git a/example/repositories/kustomization.yaml b/example/repositories/kustomization.yaml index 834d6c23..903aa370 100644 --- a/example/repositories/kustomization.yaml +++ b/example/repositories/kustomization.yaml @@ -6,6 +6,6 @@ resources: - ./apis-repository.yaml - ./local-reference-repository.yaml - ./otk-customizations-single.yaml - - ./otk-customizations-dmz.yaml - - ./otk-customizations-internal.yaml +# - ./otk-customizations-dmz.yaml +# - ./otk-customizations-internal.yaml - ../base diff --git a/example/repositories/otk-customizations-dmz.yaml b/example/repositories/otk-customizations-dmz.yaml new file mode 100644 index 00000000..0b9b755b --- /dev/null +++ b/example/repositories/otk-customizations-dmz.yaml @@ -0,0 +1,10 @@ +apiVersion: security.brcmlabs.com/v1 +kind: Repository +metadata: + name: otk-customizations-dmz +spec: + enabled: true + endpoint: https://github.com/Layer7-Community/otk-customizations-dmz + branch: main + type: git + auth: {} \ No newline at end of file diff --git a/example/repositories/otk-customizations-internal.yaml b/example/repositories/otk-customizations-internal.yaml new file mode 100644 index 00000000..2cfca944 --- /dev/null +++ b/example/repositories/otk-customizations-internal.yaml @@ -0,0 +1,10 @@ +apiVersion: security.brcmlabs.com/v1 +kind: Repository +metadata: + name: otk-customizations-internal +spec: + enabled: true + endpoint: https://github.com/Layer7-Community/otk-customizations-internal + branch: main + type: git + auth: {} \ No newline at end of file From bf051ea954e19b58884a2ca92bdda206cb8376ea Mon Sep 17 00:00:00 2001 From: Gary Vermeulen Date: Fri, 13 Mar 2026 07:23:17 +0000 Subject: [PATCH 07/11] removing old ci actions --- .github/check_image_tags.sh | 13 ------------- .github/workflows/ci.yaml | 38 ------------------------------------- .github/workflows/pr.yaml | 38 ------------------------------------- 3 files changed, 89 deletions(-) delete mode 100755 .github/check_image_tags.sh delete mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/pr.yaml diff --git a/.github/check_image_tags.sh b/.github/check_image_tags.sh deleted file mode 100755 index e39b5e8c..00000000 --- a/.github/check_image_tags.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -## Determine if image tags already exist -## Do not allow overwrites -function tag_exists() { - IMAGE_TAG_BASE=${1#*/} - curl -s -f -lSL https://hub.docker.com/v2/repositories/${IMAGE_TAG_BASE}/tags/$2 &> /dev/null -} - -if tag_exists $1 $2; then - echo tag $2 already exists on $1, exiting. - exit 1 -fi diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 7a6c4940..00000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build Images -on: - push: - branches: - - main - - develop - - experimental -jobs: - build-and-push-images: - name: Build and Push Images - runs-on: ubuntu-latest - env: - VERSION: ${{ github.ref_name }} - IMAGE_TAG_BASE: docker.io/layer7api/layer7-operator - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Prepare Bases - run: make version - - name: Build and push Operator - run: make docker-build docker-push - - name: Build and push Operator Bundle - run: make bundle-build bundle-push \ No newline at end of file diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml deleted file mode 100644 index 8f52953b..00000000 --- a/.github/workflows/pr.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build PR Images -on: - pull_request: - branches: - - main - - develop - - experimental -jobs: - build-and-push-images: - name: Build and Push Images - runs-on: ubuntu-latest - env: - VERSION: ${GITHUB_HEAD_REF} - IMAGE_TAG_BASE: docker.io/layer7api/layer7-operator - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Prepare Bases - run: make version - - name: Build and push Operator - run: make docker-build docker-push - - name: Build and push Operator Bundle - run: make bundle-build bundle-push \ No newline at end of file From 307b6b12520581e37e97f36d7b1489f87e818ab6 Mon Sep 17 00:00:00 2001 From: Gary Vermeulen Date: Fri, 13 Mar 2026 09:31:16 +0000 Subject: [PATCH 08/11] updated examples --- example/Makefile | 16 ++++++++-------- example/basic/README.md | 4 ++-- example/otel-elastic/README.md | 16 ++++++++-------- example/otel-elastic/collector.yaml | 4 ++-- example/otel-elastic/components/agent.yaml | 4 ++-- example/otel-elastic/components/apm.yaml | 2 +- example/otel-elastic/components/es.yaml | 2 +- example/otel-elastic/components/filebeat.yaml | 2 +- example/otel-elastic/components/kibana.yaml | 2 +- example/otel-elastic/components/metricbeat.yaml | 2 +- .../dashboard/apim-dashboard.ndjson | 4 ++-- example/otel-elastic/instrumentation.yaml | 2 +- example/otel-lgtm/readme.md | 8 ++++---- example/otel-prometheus/README.md | 17 +++++++++-------- 14 files changed, 43 insertions(+), 42 deletions(-) diff --git a/example/Makefile b/example/Makefile index 9548e996..515ccf9e 100644 --- a/example/Makefile +++ b/example/Makefile @@ -150,15 +150,15 @@ otel-elastic-example: install cert-manager open-telemetry elastic @echo "#####################################################\n" cert-manager: - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l app=cert-manager -n cert-manager kubectl wait --for=condition=ready --timeout=600s pod -l app=cainjector -n cert-manager kubectl wait --for=condition=ready --timeout=600s pod -l app=webhook -n cert-manager elastic: - kubectl create -f https://download.elastic.co/downloads/eck/2.8.0/crds.yaml - kubectl apply -f https://download.elastic.co/downloads/eck/2.8.0/operator.yaml + kubectl create -f https://download.elastic.co/downloads/eck/3.3.1/crds.yaml + kubectl apply -f https://download.elastic.co/downloads/eck/3.3.1/operator.yaml @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l control-plane=elastic-operator -n elastic-system kubectl apply -f ./otel-elastic/components @@ -174,7 +174,7 @@ metrics-server: kubectl apply -f ./metrics-server/metrics-server-0-6-3.yaml open-telemetry: - kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.97.1/opentelemetry-operator.yaml + kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=opentelemetry-operator -n opentelemetry-operator-system @@ -195,7 +195,7 @@ prometheus-lgtm: jaeger: -kubectl create namespace observability - kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.44.0/jaeger-operator.yaml -n observability + kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l name=jaeger-operator -n observability @@ -228,11 +228,11 @@ uninstall: -kubectl delete -f ./otel-prometheus/instrumentation.yaml -kubectl delete -f ./otel-elastic/instrumentation.yaml -kubectl delete -f ./otel-prometheus/observability/jaeger - -kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.97.1/opentelemetry-operator.yaml + -kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml -helm uninstall prometheus -n monitoring -kubectl delete -k ./otel-prometheus/monitoring/grafana/ - -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml - -kubectl delete -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.44.0/jaeger-operator.yaml -n observability + -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml + -kubectl delete -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml -kubectl delete ns observability diff --git a/example/basic/README.md b/example/basic/README.md index abe2ae75..15a758a3 100644 --- a/example/basic/README.md +++ b/example/basic/README.md @@ -170,8 +170,8 @@ You will see an init container starting with graphman-static-init. Init Containers: graphman-static-init-c1b58adb6d: Container ID: containerd://21924ae85d25437d3634ea5da1415c9bb58d678600f9fd67d4f0b0360857d7c5 - Image: docker.io/layer7api/graphman-static-init:1.0.0 - Image ID: docker.io/layer7api/graphman-static-init@sha256:24189a432c0283845664c6fd54c3e8d9f86ad9d35ef12714bb3a18b7aba85aa4 + Image: docker.io/caapim/graphman-static-init:1.0.4 + Image ID: docker.io/caapim/graphman-static-init@sha256:8cb1035035b18fa9dc2c95e2b584c758e78909b3f615ee5f49dce166e8aae213 Port: Host Port: State: Terminated diff --git a/example/otel-elastic/README.md b/example/otel-elastic/README.md index 1f9544ac..7d744214 100644 --- a/example/otel-elastic/README.md +++ b/example/otel-elastic/README.md @@ -149,7 +149,7 @@ If you use Quickstart you do not need to install/deploy any additional resources The container gateway configuration required for this integration is relatively simple. We will set some environment variables in our OTel [instrumentation](./instrumentation.yaml) that the Otel Agent present on the Container Gateway will use to send logs, traces and metrics to the Otel Collector sidecar. ``` -apiVersion: opentelemetry.io/v1alpha1 +apiVersion: opentelemetry.io/v1beta1 kind: Instrumentation metadata: name: otel-instrumentation @@ -398,7 +398,7 @@ export elasticPass=$(kubectl get secret quickstart-es-elastic-user -o go-templat ``` Create Dashboard ``` -curl -XPOST https://kibana.brcmlabs.com/api/saved_objects/_import?createNewCopies=false -H "kbn-xsrf: true" -k -uelastic:$elasticPass -F "file=@./otel-elastic/dashboard/apim-dashboard.ndjson" +curl -XPOST "https://kibana.brcmlabs.com/api/saved_objects/_import?createNewCopies=false" -H "kbn-xsrf: true" -k -uelastic:$elasticPass -F "file=@./otel-elastic/dashboard/apim-dashboard.ndjson" ``` - wait for all components to be ready @@ -428,7 +428,7 @@ You can now move on to test your gateway deployment! ### Install Cert Manager These steps are based the official documentation for installing Cert-Manager [here](https://cert-manager.io/docs/installation/). Cert-Manager is a pre-requisite for the Open Telemetry Operator. ``` -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml ``` #### View CertManager Components @@ -462,7 +462,7 @@ These steps are based the official documentation for installing Open Telemetry [ - Install the Open Telemetry Operator. ``` -kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.76.1/opentelemetry-operator.yaml +kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml ``` ##### View Open Telemetry Components @@ -677,8 +677,8 @@ kubectl describe pods ssg-57d96567cb-n24g9 Init Containers: graphman-static-init-c1b58adb6d: Container ID: containerd://21924ae85d25437d3634ea5da1415c9bb58d678600f9fd67d4f0b0360857d7c5 - Image: docker.io/layer7api/graphman-static-init:1.0.0 - Image ID: docker.io/layer7api/graphman-static-init@sha256:24189a432c0283845664c6fd54c3e8d9f86ad9d35ef12714bb3a18b7aba85aa4 + Image: docker.io/caapim/graphman-static-init:1.0.4 + Image ID: docker.io/caapim/graphman-static-init@sha256:8cb1035035b18fa9dc2c95e2b584c758e78909b3f615ee5f49dce166e8aae213 Port: Host Port: State: Terminated @@ -857,8 +857,8 @@ kubectl delete -f ./example/otel-elastic/collector.yaml kubectl delete -f ./example/otel-elastic/instrumentation.yaml kubectl delete -f https://download.elastic.co/downloads/eck/2.8.0/crds.yaml kubectl delete -f https://download.elastic.co/downloads/eck/2.8.0/operator.yaml -kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.76.1/opentelemetry-operator.yaml -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml kubectl delete -k ./example/repositories/ kubectl delete -f ./example/gateway/otel-elastic-gateway.yaml diff --git a/example/otel-elastic/collector.yaml b/example/otel-elastic/collector.yaml index bb714338..1888f9ad 100644 --- a/example/otel-elastic/collector.yaml +++ b/example/otel-elastic/collector.yaml @@ -3,9 +3,9 @@ kind: OpenTelemetryCollector metadata: name: ssg-eck spec: - image: otel/opentelemetry-collector-contrib:0.97.0 + image: otel/opentelemetry-collector-contrib:0.147.0 mode: sidecar - config: | + config: receivers: otlp: protocols: diff --git a/example/otel-elastic/components/agent.yaml b/example/otel-elastic/components/agent.yaml index a87cf6fc..3f5efdd1 100644 --- a/example/otel-elastic/components/agent.yaml +++ b/example/otel-elastic/components/agent.yaml @@ -3,7 +3,7 @@ kind: Agent metadata: name: elastic-agent spec: - version: 8.8.2 + version: 9.3.1 elasticsearchRefs: - name: quickstart daemonSet: @@ -38,7 +38,7 @@ spec: meta: package: name: kubernetes - version: 0.2.8 + version: 1.35.2 data_stream: namespace: k8s streams: diff --git a/example/otel-elastic/components/apm.yaml b/example/otel-elastic/components/apm.yaml index 092b1f82..95648831 100644 --- a/example/otel-elastic/components/apm.yaml +++ b/example/otel-elastic/components/apm.yaml @@ -3,7 +3,7 @@ kind: ApmServer metadata: name: apm-server-quickstart spec: - version: 8.8.2 + version: 9.3.1 count: 1 elasticsearchRef: name: quickstart diff --git a/example/otel-elastic/components/es.yaml b/example/otel-elastic/components/es.yaml index 3c966781..5a5236e5 100644 --- a/example/otel-elastic/components/es.yaml +++ b/example/otel-elastic/components/es.yaml @@ -57,4 +57,4 @@ spec: certificate: {} updateStrategy: changeBudget: {} - version: 8.8.2 + version: 9.3.1 diff --git a/example/otel-elastic/components/filebeat.yaml b/example/otel-elastic/components/filebeat.yaml index cfc1347a..f6771a4f 100644 --- a/example/otel-elastic/components/filebeat.yaml +++ b/example/otel-elastic/components/filebeat.yaml @@ -4,7 +4,7 @@ metadata: name: filebeat spec: type: filebeat - version: 8.8.2 + version: 9.3.1 elasticsearchRef: name: quickstart kibanaRef: diff --git a/example/otel-elastic/components/kibana.yaml b/example/otel-elastic/components/kibana.yaml index 7e0062f6..6077a77a 100644 --- a/example/otel-elastic/components/kibana.yaml +++ b/example/otel-elastic/components/kibana.yaml @@ -3,7 +3,7 @@ kind: Kibana metadata: name: quickstart spec: - version: 8.8.2 + version: 9.3.1 count: 1 elasticsearchRef: name: quickstart diff --git a/example/otel-elastic/components/metricbeat.yaml b/example/otel-elastic/components/metricbeat.yaml index 16d3216f..b558d318 100644 --- a/example/otel-elastic/components/metricbeat.yaml +++ b/example/otel-elastic/components/metricbeat.yaml @@ -4,7 +4,7 @@ metadata: name: metricbeat spec: type: metricbeat - version: 8.8.2 + version: 9.3.1 elasticsearchRef: name: quickstart kibanaRef: diff --git a/example/otel-elastic/dashboard/apim-dashboard.ndjson b/example/otel-elastic/dashboard/apim-dashboard.ndjson index 5321762c..85cce660 100644 --- a/example/otel-elastic/dashboard/apim-dashboard.ndjson +++ b/example/otel-elastic/dashboard/apim-dashboard.ndjson @@ -1,3 +1,3 @@ -{"attributes":{"allowNoIndex":true,"fieldAttrs":"{\"layer7_service_routing_latency\":{\"count\":5},\"broadcom_totalBackendLatency\":{\"count\":1},\"@timestamp\":{\"count\":1},\"layer7_totalBackendLatency\":{\"count\":2},\"layer7_totalBackendLatencyV1\":{\"count\":1},\"layer7_totalFrontendLatency\":{\"count\":1},\"db.client.connections.usage\":{\"count\":2},\"labels.pool_name\":{\"count\":1},\"labels.state\":{\"count\":1},\"process.runtime.jvm.classes.loaded\":{\"count\":2},\"db.client.connections.pending_requests\":{\"count\":1},\"labels.process_runtime_description\":{\"count\":2},\"process.runtime.jvm.gc.duration\":{\"count\":1},\"layer7_service_total\":{\"count\":1},\"process.runtime.jvm.memory.limit\":{\"count\":2},\"process.runtime.jvm.memory.usage\":{\"count\":1},\"process.runtime.jvm.system.cpu.load_1m\":{\"count\":1},\"layer7_routing_failues\":{\"count\":1},\"layer7_service_success\":{\"count\":1},\"layer7_service_latency\":{\"count\":2},\"labels.serviceName\":{\"count\":1},\"labels.gc\":{\"count\":1},\"labels.method\":{\"count\":1},\"numeric_labels.code\":{\"count\":1},\"process.runtime.jvm.threads.count\":{\"count\":1},\"span.name\":{\"count\":3},\"l7_service_latency\":{\"count\":1},\"l7_service_policy_violations\":{\"count\":1},\"labels.l7_serviceName\":{\"count\":1},\"labels.layer7gw_name\":{\"count\":1}}","fieldFormatMap":"{\"trace.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/trace/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/transaction/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"showSuffix\":true,\"useShortSuffix\":true,\"outputPrecision\":2,\"includeSpaceWithSuffix\":true}}}","fields":"[]","name":"APM","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:23:11.980Z","id":"apm_static_index_pattern_id","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-14T07:23:11.980Z","version":"WzQyMiwxXQ=="} -{"attributes":{"controlGroupInput":{"chainingSystem":"HIERARCHICAL","controlStyle":"twoLine","ignoreParentSettingsJSON":"{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}","panelsJSON":"{\"d3e0d4d0-b1de-465c-8772-806a49213c2b\":{\"type\":\"optionsListControl\",\"order\":0,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"id\":\"d3e0d4d0-b1de-465c-8772-806a49213c2b\",\"fieldName\":\"labels.layer7gw_name\",\"title\":\"Gateway\",\"selectedOptions\":[],\"enhancements\":{},\"existsSelected\":false}}}"},"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":22,\"h\":11,\"i\":\"675c235a-535c-48d9-a5ed-848810986e5a\"},\"panelIndex\":\"675c235a-535c-48d9-a5ed-848810986e5a\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\",\"isTransposed\":false,\"summaryRow\":\"sum\"},{\"columnId\":\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ff0032fe-94ec-4bf8-a50f-9905301868f9\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"isTransposed\":false}],\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"layerType\":\"data\",\"paging\":{\"size\":10,\"enabled\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"ed1df4b3-0bcd-4059-965d-39bc63eef215\":{\"label\":\"Service Name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.l7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]},\"customLabel\":true},\"82dab02b-8829-488e-bd30-aa8c45996590\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"l7_service_policy_violations\",\"filter\":{\"query\":\"l7_service_policy_violations: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"l7_service_success\",\"filter\":{\"query\":\"l7_service_success: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ff0032fe-94ec-4bf8-a50f-9905301868f9\":{\"label\":\"Routing Failures\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"l7_service_routing_failures\",\"filter\":{\"query\":\"l7_service_routing_failures: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"82dab02b-8829-488e-bd30-aa8c45996590\",\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"ff0032fe-94ec-4bf8-a50f-9905301868f9\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":0,\"w\":26,\"h\":11,\"i\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\"},\"panelIndex\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"accessors\":[\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\"],\"position\":\"top\",\"seriesType\":\"line\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"splitAccessor\":\"2bffabd5-d07e-4187-a67d-c1d085ea598a\"}],\"curveType\":\"CURVE_MONOTONE_X\",\"fittingFunction\":\"Zero\",\"emphasizeFitting\":true,\"endValue\":\"Zero\",\"valuesInLegend\":false,\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"yTitle\":\"Requests\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"cad7aae7-308f-4c4f-abbd-b03792fbec56\":{\"columns\":{\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"2bffabd5-d07e-4187-a67d-c1d085ea598a\":{\"label\":\"Top 10 values of labels.l7_serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.l7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}},\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\":{\"label\":\"Requests\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"82148cde-9560-4544-a841-38ccaf7b66f5\":{\"label\":\"Maximum of l7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"l7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"2bffabd5-d07e-4187-a67d-c1d085ea598a\",\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\",\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Message Rate\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":11,\"w\":22,\"h\":11,\"i\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\"},\"panelIndex\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Latency (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"fd66544d-5ee7-4f36-8966-c7aa38197646\",\"accessors\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"splitAccessor\":\"4ef8265b-6ee1-449f-9ced-ae065efc7296\"}],\"showCurrentTimeMarker\":false,\"yLeftScale\":\"linear\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"fd66544d-5ee7-4f36-8966-c7aa38197646\":{\"columns\":{\"c973fd1a-9645-46a1-ad45-483cf1ff0441\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"l7_service_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"l7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"subtract\",\"args\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"location\":{\"min\":0,\"max\":63},\"text\":\"average(l7_service_latency)-average(l7_service_routing_latency)\"}},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\":{\"label\":\"Frontend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(l7_service_latency)-average(l7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\"],\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\":{\"label\":\"Part of Backend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"l7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\":{\"label\":\"Backend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(l7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"customLabel\":true},\"4ef8265b-6ee1-449f-9ced-ae065efc7296\":{\"label\":\"Top 10 values of labels.l7_serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.l7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}}},\"columnOrder\":[\"4ef8265b-6ee1-449f-9ced-ae065efc7296\",\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Latency\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":11,\"w\":26,\"h\":11,\"i\":\"d9305573-c761-4802-9c0b-6f6326266125\"},\"panelIndex\":\"d9305573-c761-4802-9c0b-6f6326266125\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Number of Messages\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"seriesType\":\"bar\",\"accessors\":[\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"231179ca-b88a-4495-b857-8006bca2b409\"],\"layerType\":\"data\",\"yConfig\":[{\"forAccessor\":\"231179ca-b88a-4495-b857-8006bca2b409\",\"color\":\"#8b8c2b\",\"axisMode\":\"left\"},{\"forAccessor\":\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"color\":\"#2da275\",\"axisMode\":\"left\"},{\"forAccessor\":\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"color\":\"#781032\",\"axisMode\":\"left\"}],\"xAccessor\":\"227364f6-80d8-4d15-89ca-7166dc900804\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"227364f6-80d8-4d15-89ca-7166dc900804\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"m\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"231179ca-b88a-4495-b857-8006bca2b409\":{\"label\":\"Routing failues\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"6e76dd16-427c-4267-a977-522b4f1a8eba\"],\"customLabel\":true,\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}}},\"6e76dd16-427c-4267-a977-522b4f1a8eba\":{\"label\":\"Maximum of l7_service_routing_failures\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"l7_service_routing_failures\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"84104e54-bd82-47f6-b34d-b04a1db60694\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"84104e54-bd82-47f6-b34d-b04a1db60694\":{\"label\":\"Maximum of l7_service_policy_violations\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"l7_service_policy_violations\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"28a49db2-d0b2-47ea-bd58-12e827e9851d\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"3da518c4-50c1-415f-8248-b11b5be8873f\":{\"label\":\"Maximum of l7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"l7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"227364f6-80d8-4d15-89ca-7166dc900804\",\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"231179ca-b88a-4495-b857-8006bca2b409\",\"6e76dd16-427c-4267-a977-522b4f1a8eba\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"84104e54-bd82-47f6-b34d-b04a1db60694\",\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Service Status\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":22,\"w\":48,\"h\":15,\"i\":\"868932e9-487a-408f-89fd-14f9324534e9\"},\"panelIndex\":\"868932e9-487a-408f-89fd-14f9324534e9\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\",\"isInside\":false,\"verticalAlignment\":\"bottom\",\"horizontalAlignment\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"Zero\",\"curveType\":\"CURVE_MONOTONE_X\",\"yLeftScale\":\"sqrt\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar\",\"layers\":[{\"layerId\":\"2381ace6-24bb-4e47-b57b-6fe06c980216\",\"accessors\":[\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"splitAccessor\":\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"collapseFn\":\"\"},{\"layerId\":\"06696642-3076-4612-8291-189dcb25dd9e\",\"layerType\":\"data\",\"accessors\":[\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"seriesType\":\"line\",\"xAccessor\":\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"splitAccessor\":\"8756242d-1a48-4550-9fd5-66057b194995\"}],\"emphasizeFitting\":true,\"yTitle\":\"Connections\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"2381ace6-24bb-4e47-b57b-6fe06c980216\":{\"columns\":{\"b8a1138e-65ed-4595-8c72-23c294313fc5\":{\"label\":\"Top values of labels.pool_name + 1 other\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"b13dba51-c5c9-4c20-9212-e8607d048660\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"multi_terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[\"labels.state\"]}},\"209b6a96-6641-42d5-b243-cd7a68dc55be\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"b13dba51-c5c9-4c20-9212-e8607d048660\":{\"label\":\"conns\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.usage\",\"filter\":{\"query\":\"db.client.connections.usage: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"incompleteColumns\":{},\"sampling\":1},\"06696642-3076-4612-8291-189dcb25dd9e\":{\"linkToLayers\":[],\"columns\":{\"74470d07-35e7-40c2-bf78-7bd282323271\":{\"label\":\"pending requests\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.pending_requests\",\"filter\":{\"query\":\"db.client.connections.pending_requests: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"96a4345f-06e4-4252-9ed1-31ed39ee347c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"8756242d-1a48-4550-9fd5-66057b194995\":{\"label\":\"Top 3 values of labels.pool_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"74470d07-35e7-40c2-bf78-7bd282323271\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"8756242d-1a48-4550-9fd5-66057b194995\",\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"DB Connections\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":37,\"w\":22,\"h\":10,\"i\":\"9a54b939-5371-49f6-8888-ab92e165e625\"},\"panelIndex\":\"9a54b939-5371-49f6-8888-ab92e165e625\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"accessors\":[\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"yConfig\":[{\"forAccessor\":\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"color\":\"#e7664c\"}]}],\"yTitle\":\"CPU utilization (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\":{\"columns\":{\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f0c37a83-1464-4350-bb91-e57c2198b927X0\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927X1\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"multiply\",\"args\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",100],\"location\":{\"min\":0,\"max\":121},\"text\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\"}},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\"],\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927\":{\"label\":\"System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\",\"isFormulaBroken\":false},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X1\"],\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\":{\"label\":\"Part of JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149\":{\"label\":\"JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.cpu.utilization, kql='process.runtime.jvm.cpu.utilization: *')\",\"isFormulaBroken\":false},\"references\":[\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"customLabel\":true}},\"columnOrder\":[\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",\"f0c37a83-1464-4350-bb91-e57c2198b927X1\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"CPU/System Utilization\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":37,\"w\":26,\"h\":10,\"i\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\"},\"panelIndex\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"accessors\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"splitAccessor\":\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"palette\":{\"type\":\"palette\",\"name\":\"status\"},\"collapseFn\":\"\",\"yConfig\":[]},{\"layerId\":\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"layerType\":\"referenceLine\",\"accessors\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\"],\"yConfig\":[{\"forAccessor\":\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"axisMode\":\"left\",\"icon\":\"bolt\",\"iconPosition\":\"auto\",\"lineWidth\":2,\"color\":\"#9d2022\"}]},{\"layerId\":\"66ce3428-e822-4b54-afc8-69bbffd9c106\",\"layerType\":\"data\",\"accessors\":[\"323e403c-e4a1-481d-897e-907192f1cc4a\"],\"seriesType\":\"line\",\"xAccessor\":\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"yConfig\":[{\"forAccessor\":\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"color\":\"#040f12\"}]}],\"yLeftExtent\":{\"mode\":\"full\"},\"yTitle\":\"Megabytes\",\"yLeftScale\":\"log\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\":{\"columns\":{\"7e7413d7-db3d-4473-b46f-d76132f403b0\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\"}},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\"],\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\":{\"label\":\"Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"customLabel\":true},\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"accuracyMode\":true}}},\"columnOrder\":[\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"incompleteColumns\":{},\"sampling\":1},\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\":{\"linkToLayers\":[],\"columns\":{\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.limit\",\"filter\":{\"query\":\"process.runtime.jvm.memory.limit: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\"}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\"],\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81\":{\"label\":\"Max\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false,\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"customLabel\":true}},\"columnOrder\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"sampling\":1,\"incompleteColumns\":{}},\"66ce3428-e822-4b54-afc8-69bbffd9c106\":{\"linkToLayers\":[],\"columns\":{\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"323e403c-e4a1-481d-897e-907192f1cc4aX0\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: non_heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4aX1\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\",1048576],\"location\":{\"min\":0,\"max\":130},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\"}},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4a\":{\"label\":\"Non-Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX1\"],\"customLabel\":true}},\"columnOrder\":[\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"323e403c-e4a1-481d-897e-907192f1cc4aX1\",\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"enhancements\":{},\"hidePanelTitles\":false},\"title\":\"Memory Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":47,\"w\":22,\"h\":11,\"i\":\"542275ac-682e-43b8-b1ac-cf919d206e02\"},\"panelIndex\":\"542275ac-682e-43b8-b1ac-cf919d206e02\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"accessors\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241b\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\"}],\"yTitle\":\"System Load (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"28f4234a-7411-4d15-8ad9-36ac978f7e19\":{\"columns\":{\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\":{\"label\":\"Part of System Load\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.load_1m\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.load_1m: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"04481efd-b3d8-44bc-be6c-5a0af56e241b\":{\"label\":\"System Load\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.system.cpu.load_1m, kql='process.runtime.jvm.system.cpu.load_1m: *')\",\"isFormulaBroken\":false},\"references\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"customLabel\":true}},\"columnOrder\":[\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\",\"04481efd-b3d8-44bc-be6c-5a0af56e241b\",\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"System Load\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":47,\"w\":26,\"h\":11,\"i\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\"},\"panelIndex\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"accessors\":[\"603e1430-a496-4249-b2da-73a5205b964e\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"splitAccessor\":\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"yConfig\":[{\"forAccessor\":\"603e1430-a496-4249-b2da-73a5205b964e\",\"axisMode\":\"left\"}],\"palette\":{\"type\":\"palette\",\"name\":\"status\"}}],\"yLeftScale\":\"log\",\"yTitle\":\"Megabytes\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\":{\"columns\":{\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"603e1430-a496-4249-b2da-73a5205b964eX0\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage_after_last_gc\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964eX1\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\",1048576],\"location\":{\"min\":0,\"max\":154},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\"}},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\"],\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964e\":{\"label\":\"MB\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"customLabel\":true},\"a9ec0126-a45f-4f86-b783-a4820badd7d8\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"603e1430-a496-4249-b2da-73a5205b964e\",\"603e1430-a496-4249-b2da-73a5205b964eX0\",\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Memory after GC\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":58,\"w\":22,\"h\":10,\"i\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\"},\"panelIndex\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"accessors\":[\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"9c350955-95a1-4726-93fd-717b104a218c\",\"yConfig\":[{\"forAccessor\":\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\",\"color\":\"#54b399\"}]}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a3ee7019-98bb-45cd-8536-e36a8e58297e\":{\"columns\":{\"9c350955-95a1-4726-93fd-717b104a218c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\":{\"label\":\"Threads count\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.threads.count\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"process.runtime.jvm.threads.count: *\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9c350955-95a1-4726-93fd-717b104a218c\",\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Threads\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":58,\"w\":26,\"h\":10,\"i\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\"},\"panelIndex\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yRightTitle\":\"Duration (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"accessors\":[\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"yConfig\":[{\"forAccessor\":\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"axisMode\":\"left\"},{\"forAccessor\":\"4e7a6993-fcee-47ae-9599-0047809bb29e\",\"color\":\"#e7664c\",\"axisMode\":\"left\"}]}],\"yTitle\":\"Duration (ms)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a45cd02a-4c62-4b34-bdce-c702b87b4246\":{\"columns\":{\"57050bde-2c14-47d2-9dde-6e35326cf5cf\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\":{\"label\":\"Minor GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action: \\\"end of minor GC\\\" \",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":2}},\"emptyAsNull\":true},\"customLabel\":true},\"4e7a6993-fcee-47ae-9599-0047809bb29e\":{\"label\":\"Major GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action : \\\"end of major GC\\\"\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Garbage Collection\"}]","timeRestore":false,"title":"Layer7 Gateway Dashboard","version":1},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:28:39.093Z","id":"c29a9900-2161-11ee-be6c-57cbef857d54","managed":false,"references":[{"id":"apm_static_index_pattern_id","name":"675c235a-535c-48d9-a5ed-848810986e5a:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"0ae50fd8-f924-4207-b546-5d600ac701b3:indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7:indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"d9305573-c761-4802-9c0b-6f6326266125:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"9a54b939-5371-49f6-8888-ab92e165e625:indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"542275ac-682e-43b8-b1ac-cf919d206e02:indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"a3aace75-debc-4370-9954-e5a6e8ac6259:indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367:indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"4f987373-1b85-4e80-a7cc-c1c000349c5e:indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"controlGroup_d3e0d4d0-b1de-465c-8772-806a49213c2b:optionsListDataView","type":"index-pattern"}],"type":"dashboard","typeMigrationVersion":"8.7.0","updated_at":"2024-05-14T07:28:39.093Z","version":"WzE2ODksMV0="} +{"attributes":{"allowNoIndex":true,"fieldAttrs":"{\"layer7_service_routing_latency\":{\"count\":5},\"broadcom_totalBackendLatency\":{\"count\":1},\"@timestamp\":{\"count\":1},\"layer7_totalBackendLatency\":{\"count\":2},\"layer7_totalBackendLatencyV1\":{\"count\":1},\"layer7_totalFrontendLatency\":{\"count\":1},\"db.client.connections.usage\":{\"count\":2},\"labels.pool_name\":{\"count\":1},\"labels.state\":{\"count\":1},\"process.runtime.jvm.classes.loaded\":{\"count\":2},\"db.client.connections.pending_requests\":{\"count\":1},\"labels.process_runtime_description\":{\"count\":2},\"process.runtime.jvm.gc.duration\":{\"count\":1},\"layer7_service_total\":{\"count\":1},\"process.runtime.jvm.memory.limit\":{\"count\":2},\"process.runtime.jvm.memory.usage\":{\"count\":1},\"process.runtime.jvm.system.cpu.load_1m\":{\"count\":1},\"layer7_routing_failues\":{\"count\":1},\"layer7_service_success\":{\"count\":1},\"layer7_service_latency\":{\"count\":2},\"labels.serviceName\":{\"count\":1},\"labels.gc\":{\"count\":1},\"labels.method\":{\"count\":1},\"numeric_labels.code\":{\"count\":1},\"process.runtime.jvm.threads.count\":{\"count\":1},\"span.name\":{\"count\":3},\"layer7_service_latency\":{\"count\":1},\"layer7_service_policy_violations\":{\"count\":1},\"labels.serviceName\":{\"count\":1},\"labels.layer7gw_name\":{\"count\":1}}","fieldFormatMap":"{\"trace.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/trace/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/transaction/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"showSuffix\":true,\"useShortSuffix\":true,\"outputPrecision\":2,\"includeSpaceWithSuffix\":true}}}","fields":"[]","name":"APM","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:23:11.980Z","id":"apm_static_index_pattern_id","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-14T07:23:11.980Z","version":"WzQyMiwxXQ=="} +{"attributes":{"controlGroupInput":{"chainingSystem":"HIERARCHICAL","controlStyle":"twoLine","ignoreParentSettingsJSON":"{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}","panelsJSON":"{\"d3e0d4d0-b1de-465c-8772-806a49213c2b\":{\"type\":\"optionsListControl\",\"order\":0,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"id\":\"d3e0d4d0-b1de-465c-8772-806a49213c2b\",\"fieldName\":\"labels.layer7gw_name\",\"title\":\"Gateway\",\"selectedOptions\":[],\"enhancements\":{},\"existsSelected\":false}}}"},"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":22,\"h\":11,\"i\":\"675c235a-535c-48d9-a5ed-848810986e5a\"},\"panelIndex\":\"675c235a-535c-48d9-a5ed-848810986e5a\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\",\"isTransposed\":false,\"summaryRow\":\"sum\"},{\"columnId\":\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ff0032fe-94ec-4bf8-a50f-9905301868f9\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"isTransposed\":false}],\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"layerType\":\"data\",\"paging\":{\"size\":10,\"enabled\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"ed1df4b3-0bcd-4059-965d-39bc63eef215\":{\"label\":\"Service Name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]},\"customLabel\":true},\"82dab02b-8829-488e-bd30-aa8c45996590\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_policy_violations\",\"filter\":{\"query\":\"layer7_service_policy_violations: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_success\",\"filter\":{\"query\":\"layer7_service_success: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ff0032fe-94ec-4bf8-a50f-9905301868f9\":{\"label\":\"Routing Failures\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_routing_failures\",\"filter\":{\"query\":\"layer7_service_routing_failures: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"82dab02b-8829-488e-bd30-aa8c45996590\",\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"ff0032fe-94ec-4bf8-a50f-9905301868f9\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":0,\"w\":26,\"h\":11,\"i\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\"},\"panelIndex\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"accessors\":[\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\"],\"position\":\"top\",\"seriesType\":\"line\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"splitAccessor\":\"2bffabd5-d07e-4187-a67d-c1d085ea598a\"}],\"curveType\":\"CURVE_MONOTONE_X\",\"fittingFunction\":\"Zero\",\"emphasizeFitting\":true,\"endValue\":\"Zero\",\"valuesInLegend\":false,\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"yTitle\":\"Requests\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"cad7aae7-308f-4c4f-abbd-b03792fbec56\":{\"columns\":{\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"2bffabd5-d07e-4187-a67d-c1d085ea598a\":{\"label\":\"Top 10 values of labels.serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}},\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\":{\"label\":\"Requests\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"82148cde-9560-4544-a841-38ccaf7b66f5\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"2bffabd5-d07e-4187-a67d-c1d085ea598a\",\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\",\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Message Rate\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":11,\"w\":22,\"h\":11,\"i\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\"},\"panelIndex\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Latency (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"fd66544d-5ee7-4f36-8966-c7aa38197646\",\"accessors\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"splitAccessor\":\"4ef8265b-6ee1-449f-9ced-ae065efc7296\"}],\"showCurrentTimeMarker\":false,\"yLeftScale\":\"linear\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"fd66544d-5ee7-4f36-8966-c7aa38197646\":{\"columns\":{\"c973fd1a-9645-46a1-ad45-483cf1ff0441\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"subtract\",\"args\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"location\":{\"min\":0,\"max\":63},\"text\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\"}},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\":{\"label\":\"Frontend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\"],\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\":{\"label\":\"Part of Backend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\":{\"label\":\"Backend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"customLabel\":true},\"4ef8265b-6ee1-449f-9ced-ae065efc7296\":{\"label\":\"Top 10 values of labels.serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}}},\"columnOrder\":[\"4ef8265b-6ee1-449f-9ced-ae065efc7296\",\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Latency\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":11,\"w\":26,\"h\":11,\"i\":\"d9305573-c761-4802-9c0b-6f6326266125\"},\"panelIndex\":\"d9305573-c761-4802-9c0b-6f6326266125\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Number of Messages\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"seriesType\":\"bar\",\"accessors\":[\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"231179ca-b88a-4495-b857-8006bca2b409\"],\"layerType\":\"data\",\"yConfig\":[{\"forAccessor\":\"231179ca-b88a-4495-b857-8006bca2b409\",\"color\":\"#8b8c2b\",\"axisMode\":\"left\"},{\"forAccessor\":\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"color\":\"#2da275\",\"axisMode\":\"left\"},{\"forAccessor\":\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"color\":\"#781032\",\"axisMode\":\"left\"}],\"xAccessor\":\"227364f6-80d8-4d15-89ca-7166dc900804\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"227364f6-80d8-4d15-89ca-7166dc900804\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"m\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"231179ca-b88a-4495-b857-8006bca2b409\":{\"label\":\"Routing failues\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"6e76dd16-427c-4267-a977-522b4f1a8eba\"],\"customLabel\":true,\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}}},\"6e76dd16-427c-4267-a977-522b4f1a8eba\":{\"label\":\"Maximum of layer7_service_routing_failures\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_routing_failures\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"84104e54-bd82-47f6-b34d-b04a1db60694\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"84104e54-bd82-47f6-b34d-b04a1db60694\":{\"label\":\"Maximum of layer7_service_policy_violations\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_policy_violations\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"28a49db2-d0b2-47ea-bd58-12e827e9851d\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"3da518c4-50c1-415f-8248-b11b5be8873f\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"227364f6-80d8-4d15-89ca-7166dc900804\",\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"231179ca-b88a-4495-b857-8006bca2b409\",\"6e76dd16-427c-4267-a977-522b4f1a8eba\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"84104e54-bd82-47f6-b34d-b04a1db60694\",\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Service Status\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":22,\"w\":48,\"h\":15,\"i\":\"868932e9-487a-408f-89fd-14f9324534e9\"},\"panelIndex\":\"868932e9-487a-408f-89fd-14f9324534e9\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\",\"isInside\":false,\"verticalAlignment\":\"bottom\",\"horizontalAlignment\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"Zero\",\"curveType\":\"CURVE_MONOTONE_X\",\"yLeftScale\":\"sqrt\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar\",\"layers\":[{\"layerId\":\"2381ace6-24bb-4e47-b57b-6fe06c980216\",\"accessors\":[\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"splitAccessor\":\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"collapseFn\":\"\"},{\"layerId\":\"06696642-3076-4612-8291-189dcb25dd9e\",\"layerType\":\"data\",\"accessors\":[\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"seriesType\":\"line\",\"xAccessor\":\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"splitAccessor\":\"8756242d-1a48-4550-9fd5-66057b194995\"}],\"emphasizeFitting\":true,\"yTitle\":\"Connections\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"2381ace6-24bb-4e47-b57b-6fe06c980216\":{\"columns\":{\"b8a1138e-65ed-4595-8c72-23c294313fc5\":{\"label\":\"Top values of labels.pool_name + 1 other\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"b13dba51-c5c9-4c20-9212-e8607d048660\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"multi_terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[\"labels.state\"]}},\"209b6a96-6641-42d5-b243-cd7a68dc55be\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"b13dba51-c5c9-4c20-9212-e8607d048660\":{\"label\":\"conns\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.usage\",\"filter\":{\"query\":\"db.client.connections.usage: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"incompleteColumns\":{},\"sampling\":1},\"06696642-3076-4612-8291-189dcb25dd9e\":{\"linkToLayers\":[],\"columns\":{\"74470d07-35e7-40c2-bf78-7bd282323271\":{\"label\":\"pending requests\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.pending_requests\",\"filter\":{\"query\":\"db.client.connections.pending_requests: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"96a4345f-06e4-4252-9ed1-31ed39ee347c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"8756242d-1a48-4550-9fd5-66057b194995\":{\"label\":\"Top 3 values of labels.pool_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"74470d07-35e7-40c2-bf78-7bd282323271\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"8756242d-1a48-4550-9fd5-66057b194995\",\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"DB Connections\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":37,\"w\":22,\"h\":10,\"i\":\"9a54b939-5371-49f6-8888-ab92e165e625\"},\"panelIndex\":\"9a54b939-5371-49f6-8888-ab92e165e625\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"accessors\":[\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"yConfig\":[{\"forAccessor\":\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"color\":\"#e7664c\"}]}],\"yTitle\":\"CPU utilization (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\":{\"columns\":{\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f0c37a83-1464-4350-bb91-e57c2198b927X0\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927X1\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"multiply\",\"args\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",100],\"location\":{\"min\":0,\"max\":121},\"text\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\"}},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\"],\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927\":{\"label\":\"System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\",\"isFormulaBroken\":false},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X1\"],\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\":{\"label\":\"Part of JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149\":{\"label\":\"JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.cpu.utilization, kql='process.runtime.jvm.cpu.utilization: *')\",\"isFormulaBroken\":false},\"references\":[\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"customLabel\":true}},\"columnOrder\":[\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",\"f0c37a83-1464-4350-bb91-e57c2198b927X1\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"CPU/System Utilization\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":37,\"w\":26,\"h\":10,\"i\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\"},\"panelIndex\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"accessors\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"splitAccessor\":\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"palette\":{\"type\":\"palette\",\"name\":\"status\"},\"collapseFn\":\"\",\"yConfig\":[]},{\"layerId\":\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"layerType\":\"referenceLine\",\"accessors\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\"],\"yConfig\":[{\"forAccessor\":\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"axisMode\":\"left\",\"icon\":\"bolt\",\"iconPosition\":\"auto\",\"lineWidth\":2,\"color\":\"#9d2022\"}]},{\"layerId\":\"66ce3428-e822-4b54-afc8-69bbffd9c106\",\"layerType\":\"data\",\"accessors\":[\"323e403c-e4a1-481d-897e-907192f1cc4a\"],\"seriesType\":\"line\",\"xAccessor\":\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"yConfig\":[{\"forAccessor\":\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"color\":\"#040f12\"}]}],\"yLeftExtent\":{\"mode\":\"full\"},\"yTitle\":\"Megabytes\",\"yLeftScale\":\"log\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\":{\"columns\":{\"7e7413d7-db3d-4473-b46f-d76132f403b0\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\"}},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\"],\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\":{\"label\":\"Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"customLabel\":true},\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"accuracyMode\":true}}},\"columnOrder\":[\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"incompleteColumns\":{},\"sampling\":1},\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\":{\"linkToLayers\":[],\"columns\":{\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.limit\",\"filter\":{\"query\":\"process.runtime.jvm.memory.limit: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\"}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\"],\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81\":{\"label\":\"Max\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false,\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"customLabel\":true}},\"columnOrder\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"sampling\":1,\"incompleteColumns\":{}},\"66ce3428-e822-4b54-afc8-69bbffd9c106\":{\"linkToLayers\":[],\"columns\":{\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"323e403c-e4a1-481d-897e-907192f1cc4aX0\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: non_heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4aX1\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\",1048576],\"location\":{\"min\":0,\"max\":130},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\"}},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4a\":{\"label\":\"Non-Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX1\"],\"customLabel\":true}},\"columnOrder\":[\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"323e403c-e4a1-481d-897e-907192f1cc4aX1\",\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"enhancements\":{},\"hidePanelTitles\":false},\"title\":\"Memory Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":47,\"w\":22,\"h\":11,\"i\":\"542275ac-682e-43b8-b1ac-cf919d206e02\"},\"panelIndex\":\"542275ac-682e-43b8-b1ac-cf919d206e02\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"accessors\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241b\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\"}],\"yTitle\":\"System Load (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"28f4234a-7411-4d15-8ad9-36ac978f7e19\":{\"columns\":{\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\":{\"label\":\"Part of System Load\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.load_1m\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.load_1m: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"04481efd-b3d8-44bc-be6c-5a0af56e241b\":{\"label\":\"System Load\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.system.cpu.load_1m, kql='process.runtime.jvm.system.cpu.load_1m: *')\",\"isFormulaBroken\":false},\"references\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"customLabel\":true}},\"columnOrder\":[\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\",\"04481efd-b3d8-44bc-be6c-5a0af56e241b\",\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"System Load\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":47,\"w\":26,\"h\":11,\"i\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\"},\"panelIndex\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"accessors\":[\"603e1430-a496-4249-b2da-73a5205b964e\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"splitAccessor\":\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"yConfig\":[{\"forAccessor\":\"603e1430-a496-4249-b2da-73a5205b964e\",\"axisMode\":\"left\"}],\"palette\":{\"type\":\"palette\",\"name\":\"status\"}}],\"yLeftScale\":\"log\",\"yTitle\":\"Megabytes\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\":{\"columns\":{\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"603e1430-a496-4249-b2da-73a5205b964eX0\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage_after_last_gc\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964eX1\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\",1048576],\"location\":{\"min\":0,\"max\":154},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\"}},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\"],\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964e\":{\"label\":\"MB\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"customLabel\":true},\"a9ec0126-a45f-4f86-b783-a4820badd7d8\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"603e1430-a496-4249-b2da-73a5205b964e\",\"603e1430-a496-4249-b2da-73a5205b964eX0\",\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Memory after GC\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":58,\"w\":22,\"h\":10,\"i\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\"},\"panelIndex\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"accessors\":[\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"9c350955-95a1-4726-93fd-717b104a218c\",\"yConfig\":[{\"forAccessor\":\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\",\"color\":\"#54b399\"}]}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a3ee7019-98bb-45cd-8536-e36a8e58297e\":{\"columns\":{\"9c350955-95a1-4726-93fd-717b104a218c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\":{\"label\":\"Threads count\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.threads.count\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"process.runtime.jvm.threads.count: *\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9c350955-95a1-4726-93fd-717b104a218c\",\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Threads\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":58,\"w\":26,\"h\":10,\"i\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\"},\"panelIndex\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yRightTitle\":\"Duration (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"accessors\":[\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"yConfig\":[{\"forAccessor\":\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"axisMode\":\"left\"},{\"forAccessor\":\"4e7a6993-fcee-47ae-9599-0047809bb29e\",\"color\":\"#e7664c\",\"axisMode\":\"left\"}]}],\"yTitle\":\"Duration (ms)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a45cd02a-4c62-4b34-bdce-c702b87b4246\":{\"columns\":{\"57050bde-2c14-47d2-9dde-6e35326cf5cf\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\":{\"label\":\"Minor GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action: \\\"end of minor GC\\\" \",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":2}},\"emptyAsNull\":true},\"customLabel\":true},\"4e7a6993-fcee-47ae-9599-0047809bb29e\":{\"label\":\"Major GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action : \\\"end of major GC\\\"\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Garbage Collection\"}]","timeRestore":false,"title":"Layer7 Gateway Dashboard","version":1},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:28:39.093Z","id":"c29a9900-2161-11ee-be6c-57cbef857d54","managed":false,"references":[{"id":"apm_static_index_pattern_id","name":"675c235a-535c-48d9-a5ed-848810986e5a:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"0ae50fd8-f924-4207-b546-5d600ac701b3:indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7:indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"d9305573-c761-4802-9c0b-6f6326266125:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"9a54b939-5371-49f6-8888-ab92e165e625:indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"542275ac-682e-43b8-b1ac-cf919d206e02:indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"a3aace75-debc-4370-9954-e5a6e8ac6259:indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367:indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"4f987373-1b85-4e80-a7cc-c1c000349c5e:indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"controlGroup_d3e0d4d0-b1de-465c-8772-806a49213c2b:optionsListDataView","type":"index-pattern"}],"type":"dashboard","typeMigrationVersion":"8.7.0","updated_at":"2024-05-14T07:28:39.093Z","version":"WzE2ODksMV0="} {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file diff --git a/example/otel-elastic/instrumentation.yaml b/example/otel-elastic/instrumentation.yaml index f3add671..09550e28 100644 --- a/example/otel-elastic/instrumentation.yaml +++ b/example/otel-elastic/instrumentation.yaml @@ -1,4 +1,4 @@ -apiVersion: opentelemetry.io/v1alpha1 +apiVersion: opentelemetry.io/v1beta1 kind: Instrumentation metadata: name: otel-instrumentation diff --git a/example/otel-lgtm/readme.md b/example/otel-lgtm/readme.md index 36d8e3b3..f4ccd07a 100644 --- a/example/otel-lgtm/readme.md +++ b/example/otel-lgtm/readme.md @@ -260,7 +260,7 @@ the OpenTelemetry Operator is not a requirement, it makes configuring the Gatewa - Deploy Cert-Manager (OTel Operator dependency) ``` -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml ``` verify cert-manager has been successfully deployed ``` @@ -271,7 +271,7 @@ kubectl wait --for=condition=ready --timeout=600s pod -l app=webhook -n cert-man - Deploy the OpenTelemetry Operator ``` -kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.97.1/opentelemetry-operator.yaml +kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml ``` verify that the OpenTelemetry Operator has been successfully deployed ``` @@ -407,8 +407,8 @@ kubectl describe pods ssg-57d96567cb-n24g9 Init Containers: graphman-static-init-c1b58adb6d: Container ID: containerd://21924ae85d25437d3634ea5da1415c9bb58d678600f9fd67d4f0b0360857d7c5 - Image: docker.io/layer7api/graphman-static-init:1.0.0 - Image ID: docker.io/layer7api/graphman-static-init@sha256:24189a432c0283845664c6fd54c3e8d9f86ad9d35ef12714bb3a18b7aba85aa4 + Image: docker.io/caapim/graphman-static-init:1.0.4 + Image ID: docker.io/caapim/graphman-static-init@sha256:8cb1035035b18fa9dc2c95e2b584c758e78909b3f615ee5f49dce166e8aae213 Port: Host Port: State: Terminated diff --git a/example/otel-prometheus/README.md b/example/otel-prometheus/README.md index 264133a6..ed179e56 100644 --- a/example/otel-prometheus/README.md +++ b/example/otel-prometheus/README.md @@ -409,7 +409,7 @@ You can now move on to test your gateway deployment! ### Install Cert Manager These steps are based the official documentation for installing Cert-Manager [here](https://cert-manager.io/docs/installation/). Cert-Manager is a pre-requisite for the Open Telemetry Operator. ``` - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.yaml + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml ``` #### View CertManager Components @@ -443,7 +443,7 @@ These steps are based the official documentation for installing Open Telemetry [ - Install the Open Telemetry Operator. ``` -kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.97.1/opentelemetry-operator.yaml +kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml ``` #### View Open Telemetry Components @@ -500,7 +500,7 @@ kubectl apply -f ./example/otel-prometheus/servicemonitor.yaml ``` ### Install Jaeger -These steps are based on instructions that can be found in the Jaeger [documentation](https://www.jaegertracing.io/docs/1.44/operator/) +These steps are based on instructions that can be found in the Jaeger [documentation](https://www.jaegertracing.io/docs/1.65/operator/) - Create a namespace called observability ``` @@ -508,7 +508,7 @@ kubectl create namespace observability ``` - Install the Jaeger Operator ``` -kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.44.0/jaeger-operator.yaml -n observability +kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability ``` #### View Jaeger Components @@ -658,8 +658,8 @@ kubectl describe pods ssg-57d96567cb-n24g9 Init Containers: graphman-static-init-c1b58adb6d: Container ID: containerd://21924ae85d25437d3634ea5da1415c9bb58d678600f9fd67d4f0b0360857d7c5 - Image: docker.io/layer7api/graphman-static-init:1.0.0 - Image ID: docker.io/layer7api/graphman-static-init@sha256:24189a432c0283845664c6fd54c3e8d9f86ad9d35ef12714bb3a18b7aba85aa4 + Image: docker.io/caapim/graphman-static-init:1.0.4 + Image ID: docker.io/caapim/graphman-static-init@sha256:8cb1035035b18fa9dc2c95e2b584c758e78909b3f615ee5f49dce166e8aae213 Port: Host Port: State: Terminated @@ -835,8 +835,9 @@ kubectl delete -f ./example/otel-prometheus/collector.yaml kubectl delete -f ./example/otel-prometheus/instrumentation.yaml kubectl delete -f ./example/otel-prometheus/observability/jaeger/jaeger.yaml kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml -kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.76.1/opentelemetry-operator.yaml -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml +kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml +kubectl delete -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability +kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml helm uninstall prometheus -n monitoring kubectl delete ns monitoring kubectl delete ns observability From 5afdcae83536007898efbe3b519bc9037e05adb6 Mon Sep 17 00:00:00 2001 From: Gary Vermeulen Date: Thu, 19 Mar 2026 10:50:31 +0000 Subject: [PATCH 09/11] updated examples, removed ingress-nginx, added contour, updated otel examples --- example/Makefile | 98 +- example/README.md | 1 - example/advanced/README.md | 16 +- example/gateway/advanced-gateway.yaml | 12 +- example/gateway/openshift-gateway.yaml | 6 +- example/gateway/otel-elastic-gateway.yaml | 26 +- example/gateway/otel-lgtm-gateway.yaml | 17 +- .../gateway/otel-lgtm-sdk-only-gateway.yaml | 17 +- example/gateway/otel-prometheus-gateway.yaml | 201 --- example/gateway/otk/otk-dmz.yaml | 13 +- example/gateway/otk/otk-internal.yaml | 9 +- example/gateway/otk/otk-single.yaml | 37 +- example/gateway/otk/otk-ssg-dmz.yaml | 13 +- example/gateway/otk/otk-ssg-internal.yaml | 9 +- example/gateway/portal-gateway.yaml | 8 +- example/images/elastic-assets.gif | Bin 672964 -> 0 bytes example/otel-elastic/README.md | 110 +- example/otel-elastic/collector.yaml | 16 +- example/otel-elastic/components/es.yaml | 6 - example/otel-elastic/components/ingress.yaml | 6 +- example/otel-elastic/components/kibana.yaml | 3 + .../otel-elastic/components/metricbeat.yaml | 2 +- .../dashboard/apim-dashboard.ndjson | 4 +- example/otel-elastic/instrumentation.yaml | 2 +- example/otel-lgtm/collector.yaml | 20 +- .../otel-lgtm/layer7-operator/collector.yaml | 6 +- .../layer7-gateway-dashboard.json | 89 +- .../prometheus/prometheus-values.yaml | 6 +- example/otel-lgtm/readme.md | 91 +- example/otel-prometheus/README.md | 849 ---------- example/otel-prometheus/collector.yaml | 52 - example/otel-prometheus/instrumentation.yaml | 23 - .../monitoring/grafana/kustomization.yaml | 10 - .../grafana/layer7-gateway-dashboard.json | 1414 ----------------- .../prometheus/prometheus-values.yaml | 80 - .../observability/jaeger/ingress.yaml | 22 - .../observability/jaeger/jaeger.yaml | 7 - example/otel-prometheus/servicemonitor.yaml | 21 - example/otk/single/README.md | 16 +- example/portal-integration/portal-values.yaml | 9 +- example/portal-integration/readme.md | 47 +- 41 files changed, 317 insertions(+), 3077 deletions(-) delete mode 100644 example/gateway/otel-prometheus-gateway.yaml delete mode 100644 example/images/elastic-assets.gif delete mode 100644 example/otel-prometheus/README.md delete mode 100644 example/otel-prometheus/collector.yaml delete mode 100644 example/otel-prometheus/instrumentation.yaml delete mode 100644 example/otel-prometheus/monitoring/grafana/kustomization.yaml delete mode 100644 example/otel-prometheus/monitoring/grafana/layer7-gateway-dashboard.json delete mode 100644 example/otel-prometheus/monitoring/prometheus/prometheus-values.yaml delete mode 100644 example/otel-prometheus/observability/jaeger/ingress.yaml delete mode 100644 example/otel-prometheus/observability/jaeger/jaeger.yaml delete mode 100644 example/otel-prometheus/servicemonitor.yaml diff --git a/example/Makefile b/example/Makefile index 515ccf9e..7eab6a00 100644 --- a/example/Makefile +++ b/example/Makefile @@ -32,7 +32,7 @@ basic: @$(MAKE) --silent t=10 wait kubectl apply -f ./gateway/basic-gateway.yaml -advanced: default-redis +advanced: kubectl apply -k ./repositories @$(MAKE) --silent t=10 wait kubectl apply -f ./gateway/advanced-gateway.yaml @@ -61,9 +61,8 @@ portal-example: redis @echo "####################################################################################################################################" ./portal-integration/create-tenant.sh -d ./portal-integration/enroll-payload.json -n ${NAMESPACE} -# add nginx ip for portal domains so the agent can resolve them g2c-agent: - sed -e 's/ip:.*/ip: "$(shell kubectl get svc ingress-nginx-controller -n ingress-nginx -ojsonpath="{.spec.clusterIP}")"/g' ./portal-integration/g2c-agent/deployment-template.yaml > ./portal-integration/g2c-agent/deployment.yaml +#sed -e 's/ip:.*/ip: "$(shell kubectl get svc ingress-nginx-controller -n ingress-nginx -ojsonpath="{.spec.clusterIP}")"/g' ./portal-integration/g2c-agent/deployment-template.yaml > ./portal-integration/g2c-agent/deployment.yaml kubectl apply -k ./portal-integration/g2c-agent echo "deploy g2cagent" @@ -77,18 +76,15 @@ default-redis: helm upgrade -i standalone -f ./portal-integration/redis/redis-values.yaml oci://registry-1.docker.io/bitnamicharts/redis @$(MAKE) --silent t=15 wait kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=redis - -configure-nginx-ssl-passthrough: - kubectl -n ingress-nginx get deployment ingress-nginx-controller -oyaml | sed -e "s/env:/\- \-\-enable\-ssl\-passthrough\n env\:/g" | kubectl -n ingress-nginx apply -f - grafana-stack: - helm repo add grafana https://grafana.github.io/helm-charts + helm repo add grafana https://grafana-community.github.io/helm-charts helm upgrade --install --values ./otel-lgtm/grafana-stack/loki-overrides.yaml loki grafana/loki -n grafana-loki --create-namespace helm upgrade --install --values ./otel-lgtm/grafana-stack/promtail-overrides.yaml promtail grafana/promtail -n grafana-loki helm upgrade --install --values ./otel-lgtm/grafana-stack/tempo-overrides.yaml tempo grafana/tempo -n grafana-loki helm upgrade --install --values ./otel-lgtm/grafana-stack/mimir-distributed-overrides.yaml mimir grafana/mimir-distributed -n grafana-loki -otel-lgtm-example-kind: install cert-manager prometheus-lgtm open-telemetry grafana-stack nginx-kind +otel-lgtm-example-kind: install cert-manager prometheus-lgtm open-telemetry grafana-stack contour-kind kubectl apply -f ./otel-lgtm/collector.yaml kubectl apply -f ./otel-lgtm/instrumentation.yaml kubectl apply -k ./repositories @@ -103,27 +99,27 @@ otel-lgtm-example: install cert-manager prometheus-lgtm open-telemetry grafana-s @$(MAKE) --silent t=10 wait kubectl apply -f ./gateway/otel-lgtm-gateway.yaml -otel-prometheus-example-kind: install cert-manager prometheus open-telemetry jaeger nginx-kind - kubectl apply -f ./otel-prometheus/servicemonitor.yaml - kubectl apply -f ./otel-prometheus/collector.yaml - kubectl apply -f ./otel-prometheus/instrumentation.yaml - kubectl apply -f ./otel-prometheus/observability/jaeger/jaeger.yaml - kubectl apply -f ./otel-prometheus/observability/jaeger/ingress.yaml - kubectl apply -k ./repositories - @$(MAKE) --silent t=10 wait - kubectl apply -f ./gateway/otel-prometheus-gateway.yaml - -otel-prometheus-example: install cert-manager prometheus open-telemetry jaeger - kubectl apply -f ./otel-prometheus/servicemonitor.yaml - kubectl apply -f ./otel-prometheus/collector.yaml - kubectl apply -f ./otel-prometheus/instrumentation.yaml - kubectl apply -f ./otel-prometheus/observability/jaeger/jaeger.yaml - kubectl apply -f ./otel-prometheus/observability/jaeger/ingress.yaml - kubectl apply -k ./repositories - @$(MAKE) --silent t=10 wait - kubectl apply -f ./gateway/otel-prometheus-gateway.yaml - -otel-elastic-example-kind: install cert-manager open-telemetry elastic nginx-kind +# otel-prometheus-example-kind: install cert-manager prometheus open-telemetry jaeger contour-kind +# kubectl apply -f ./otel-prometheus/servicemonitor.yaml +# kubectl apply -f ./otel-prometheus/collector.yaml +# kubectl apply -f ./otel-prometheus/instrumentation.yaml +# kubectl apply -f ./otel-prometheus/observability/jaeger/jaeger.yaml +# kubectl apply -f ./otel-prometheus/observability/jaeger/ingress.yaml +# kubectl apply -k ./repositories +# @$(MAKE) --silent t=10 wait +# kubectl apply -f ./gateway/otel-prometheus-gateway.yaml + +# otel-prometheus-example: install cert-manager prometheus open-telemetry jaeger +# kubectl apply -f ./otel-prometheus/servicemonitor.yaml +# kubectl apply -f ./otel-prometheus/collector.yaml +# kubectl apply -f ./otel-prometheus/instrumentation.yaml +# kubectl apply -f ./otel-prometheus/observability/jaeger/jaeger.yaml +# kubectl apply -f ./otel-prometheus/observability/jaeger/ingress.yaml +# kubectl apply -k ./repositories +# @$(MAKE) --silent t=10 wait +# kubectl apply -f ./gateway/otel-prometheus-gateway.yaml + +otel-elastic-example-kind: install cert-manager open-telemetry elastic contour-kind @$(MAKE) --silent t=$(shell kubectl get secret/apm-server-quickstart-apm-token -o go-template='{{index .data "secret-token" | base64decode}}') create-collector kubectl apply -f ./otel-elastic/instrumentation.yaml @$(MAKE) --silent t=10 wait @@ -178,36 +174,51 @@ open-telemetry: @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=opentelemetry-operator -n opentelemetry-operator-system -prometheus: - helm repo add prometheus-community https://prometheus-community.github.io/helm-charts - helm repo update - -kubectl create ns monitoring - kubectl apply -k ./otel-prometheus/monitoring/grafana/ - helm upgrade -i prometheus -f ./otel-prometheus/monitoring/prometheus/prometheus-values.yaml prometheus-community/kube-prometheus-stack -n monitoring +# prometheus: +# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +# helm repo update +# -kubectl create ns monitoring +# kubectl apply -k ./otel-prometheus/monitoring/grafana/ +# helm upgrade -i prometheus -f ./otel-prometheus/monitoring/prometheus/prometheus-values.yaml prometheus-community/kube-prometheus-stack -n monitoring prometheus-lgtm: helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update -kubectl create ns monitoring kubectl apply -k ./otel-lgtm/prometheus/grafana-dashboard/ + @$(MAKE) NAMESPACE=monitoring pki helm upgrade -i prometheus -f ./otel-lgtm/prometheus/prometheus-values.yaml prometheus-community/kube-prometheus-stack -n monitoring - jaeger: -kubectl create namespace observability kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability @$(MAKE) --silent t=10 wait kubectl wait --for=condition=ready --timeout=600s pod -l name=jaeger-operator -n observability -nginx-kind: - kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml +contour: pki + helm repo add contour https://projectcontour.github.io/helm-charts/ + helm upgrade --install contour contour/contour --namespace projectcontour --create-namespace @$(MAKE) --silent t=10 wait - kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/component=controller -n ingress-nginx + kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=contour -n projectcontour + kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=envoy -n projectcontour -nginx: - kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml +contour-kind: pki + helm repo add contour https://projectcontour.github.io/helm-charts/ + helm upgrade --install contour contour/contour --namespace projectcontour --set envoy.useHostPort.http=true --set envoy.useHostPort.https=true --create-namespace @$(MAKE) --silent t=10 wait - kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/component=controller -n ingress-nginx + kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=contour -n projectcontour + kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=envoy -n projectcontour + +uninstall-contour: + helm uninstall contour -n projectcontour + +pki: + -openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj "/CN=*.brcmlabs.com" \ + -addext "subjectAltName=DNS:*.brcmlabs.com,DNS:brcmlabs.com" \ + -keyout /tmp/tls.key -out /tmp/tls.crt + -kubectl create secret tls brcmlabs \ + --cert=/tmp/tls.crt --key=/tmp/tls.key \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - cassandra: helm upgrade --install cassandra -f ./otk/database/cassandra/cassandra-values.yaml oci://registry-1.docker.io/bitnamicharts/cassandra @@ -233,13 +244,12 @@ uninstall: -kubectl delete -k ./otel-prometheus/monitoring/grafana/ -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml -kubectl delete -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability - -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml - -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml -kubectl delete ns observability -kubectl delete ns monitoring -kubectl delete -f https://github.com/CAAPIM/layer7-operator/releases/download/v1.2.2/bundle.yaml -helm uninstall mysql -helm uninstall cassandra + -helm uninstall contour -n projectcontour uninstall-kind: kind delete cluster --name layer7 diff --git a/example/README.md b/example/README.md index 2bf54ad0..ec73a911 100644 --- a/example/README.md +++ b/example/README.md @@ -35,7 +35,6 @@ Portal Integration Example Other examples - [Elastic Stack](./otel-elastic) -- [Prometheus](./otel-prometheus) Repositories (used in most of the examples) - [Repositories](./repositories/) \ No newline at end of file diff --git a/example/advanced/README.md b/example/advanced/README.md index d3b77961..7cd05ccc 100644 --- a/example/advanced/README.md +++ b/example/advanced/README.md @@ -13,15 +13,15 @@ By the end of this example you should have a better understanding of the Layer7 ``` 3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - these will be referenced later on. -4. You will need an ingress controller like nginx +4. You will need an ingress controller like [contour](https://projectcontour.io/) - if you do not have one installed already you can use the makefile in the example directory to deploy one - ```cd example``` - Generic Kubernetes - - ```make nginx``` + - ```make contour``` - Kind (Kubernetes in Docker) - follow the steps in Quickstart or - - ```make nginx-kind``` + - ```make contour-kind``` - return to the previous folder - ```cd ..``` @@ -38,11 +38,11 @@ cd example ``` make kind-cluster ``` -3. Deploy Nginx (based on your environment) +3. Deploy Contour (based on your environment) ``` -make nginx +make contour or -make nginx-kind +make contour-kind ``` 4. Deploy the example ``` @@ -353,8 +353,8 @@ version: 11.1.3 ``` kubectl get ingress -NAME CLASS HOSTS ADDRESS PORTS AGE -ssg nginx gateway.brcmlabs.com or localhost 80, 443 54m +NAME CLASS HOSTS ADDRESS PORTS AGE +ssg contour gateway.brcmlabs.com or localhost 80, 443 54m ``` Add the following to your hosts file for DNS resolution diff --git a/example/gateway/advanced-gateway.yaml b/example/gateway/advanced-gateway.yaml index 59c054ca..1a110910 100644 --- a/example/gateway/advanced-gateway.yaml +++ b/example/gateway/advanced-gateway.yaml @@ -140,8 +140,7 @@ spec: # Management port requires a separate service. service: enabled: false - #annotations: - # cloud.google.com/load-balancer-type: "Internal" + annotations: {} type: ClusterIP ports: - name: management @@ -159,7 +158,8 @@ spec: enabled: false # this runs the gateway in dbbacked/ephemeral mode # jdbcUrl: "jdbc:mysql://cluster1-haproxy.pxc.svc.cluster.local:3306/ssg" service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443,9443" type: LoadBalancer ports: - name: https @@ -172,10 +172,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/openshift-gateway.yaml b/example/gateway/openshift-gateway.yaml index d3b44509..faa449f6 100644 --- a/example/gateway/openshift-gateway.yaml +++ b/example/gateway/openshift-gateway.yaml @@ -142,13 +142,11 @@ spec: protocol: TCP ingress: enabled: false - ingressClassName: nginx + ingressClassName: contour type: route route: host: gateway.brcmlabs.com - annotations: - # nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + annotations: {} tls: - hosts: - gateway.brcmlabs.coms diff --git a/example/gateway/otel-elastic-gateway.yaml b/example/gateway/otel-elastic-gateway.yaml index d2e97d1e..ce8aef44 100644 --- a/example/gateway/otel-elastic-gateway.yaml +++ b/example/gateway/otel-elastic-gateway.yaml @@ -94,9 +94,8 @@ spec: secretName: gateway-secret service: enabled: true - #annotations: - # cloud.google.com/load-balancer-type: "Internal" - type: LoadBalancer + annotations: {} + type: ClusterIP ports: - name: management port: 9443 @@ -127,7 +126,7 @@ spec: - -Dcom.l7tech.server.pkix.useDefaultTrustAnchors=true - -Dcom.l7tech.security.ssl.hostAllowWildcard=true listenPorts: - harden: true + harden: false custom: enabled: true ports: @@ -253,9 +252,9 @@ spec: - name: otel.traceEnabled value: "true" - name: otel.attributePrefix - value: l7_ + value: layer7_ - name: otel.metricPrefix - value: l7_ + value: layer7_ - name: otel.traceConfig value: | { @@ -286,23 +285,18 @@ spec: otel.java.global-autoconfigure.enabled=true # Additional properties go here service: - # annotations: - type: LoadBalancer + annotations: + projectcontour.io/upstream-protocol.tls: "8443" + type: ClusterIP ports: - name: https port: 8443 targetPort: 8443 protocol: TCP - - name: monitoring - port: 8889 - targetPort: 8889 - protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/otel-lgtm-gateway.yaml b/example/gateway/otel-lgtm-gateway.yaml index 1ee781df..116786c5 100644 --- a/example/gateway/otel-lgtm-gateway.yaml +++ b/example/gateway/otel-lgtm-gateway.yaml @@ -91,8 +91,8 @@ spec: secretName: gateway-secret service: enabled: true - #annotations: - # cloud.google.com/load-balancer-type: "Internal" + annotations: + projectcontour.io/upstream-protocol.tls: "9443" type: LoadBalancer ports: - name: management @@ -250,9 +250,9 @@ spec: - name: otel.traceEnabled value: "true" - name: otel.attributePrefix - value: l7_ + value: layer7_ - name: otel.metricPrefix - value: l7_ + value: layer7_ - name: otel.traceConfig value: | { @@ -283,7 +283,8 @@ spec: otel.java.global-autoconfigure.enabled=true # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: LoadBalancer ports: - name: https @@ -292,10 +293,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/otel-lgtm-sdk-only-gateway.yaml b/example/gateway/otel-lgtm-sdk-only-gateway.yaml index e41b7e1c..1acb9a87 100644 --- a/example/gateway/otel-lgtm-sdk-only-gateway.yaml +++ b/example/gateway/otel-lgtm-sdk-only-gateway.yaml @@ -89,8 +89,8 @@ spec: secretName: gateway-secret service: enabled: true - #annotations: - # cloud.google.com/load-balancer-type: "Internal" + annotations: + projectcontour.io/upstream-protocol.tls: "9443" type: LoadBalancer ports: - name: management @@ -248,9 +248,9 @@ spec: - name: otel.traceEnabled value: "true" - name: otel.attributePrefix - value: l7_ + value: layer7_ - name: otel.metricPrefix - value: l7_ + value: layer7_ - name: otel.traceConfig value: | { @@ -281,7 +281,8 @@ spec: otel.java.global-autoconfigure.enabled=true # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: LoadBalancer ports: - name: https @@ -290,10 +291,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/otel-prometheus-gateway.yaml b/example/gateway/otel-prometheus-gateway.yaml deleted file mode 100644 index 42ea05ec..00000000 --- a/example/gateway/otel-prometheus-gateway.yaml +++ /dev/null @@ -1,201 +0,0 @@ -apiVersion: security.brcmlabs.com/v1 -kind: Gateway -metadata: - name: ssg -spec: - version: "11.1.3" - license: - accept: false - secretName: gateway-license - app: - podAnnotations: - sidecar.opentelemetry.io/inject: "ssg-prom" - instrumentation.opentelemetry.io/inject-java: "true" - instrumentation.opentelemetry.io/container-names: "gateway" - replicas: 1 - image: docker.io/caapim/gateway:11.1.3 - imagePullPolicy: IfNotPresent - updateStrategy: - type: rollingUpdate - rollingUpdate: - maxUnavailable: 0 - maxSurge: 2 - resources: - requests: {} - limits: {} - autoscaling: - enabled: false - bundle: - - type: graphman - source: secret - name: global-bundle - repositoryReferences: - - name: l7-gw-myframework - enabled: true - type: static - encryption: - existingSecret: graphman-encryption-secret - key: FRAMEWORK_ENCRYPTION_PASSPHRASE - - name: l7-gw-myapis - enabled: true - type: dynamic - encryption: - existingSecret: graphman-encryption-secret - key: APIS_ENCRYPTION_PASSPHRASE - - name: l7-gw-mysubscriptions - enabled: true - type: dynamic - encryption: - existingSecret: graphman-encryption-secret - key: SUBSCRIPTIONS_ENCRYPTION_PASSPHRASE - bootstrap: - script: - enabled: false - initContainers: [] - hazelcast: - external: false - endpoint: hazelcast.example.com:5701 - management: - secretName: gateway-secret - #username: admin - #password: 7layer - # Management port requires a separate service... - service: - enabled: false - #annotations: - # cloud.google.com/load-balancer-type: "Internal" - type: ClusterIP - ports: - - name: management - port: 9443 - targetPort: 9443 - protocol: TCP - restman: - enabled: false - graphman: - enabled: true - initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 - cluster: - #password: 7layer - hostname: gateway.brcmlabs.com - database: - enabled: false # this runs the gateway in dbbacked/ephemeral mode - # jdbcUrl: "jdbc:mysql://cluster1-haproxy.pxc.svc.cluster.local:3306/ssg" - # username: "gateway" - # password: "ACm8BDr3Rfk2Flx9V" - java: - jvmHeap: - calculate: true - percentage: 50 - default: 2g - extraArgs: - - -Dcom.l7tech.bootstrap.autoTrustSslKey=trustAnchor,TrustedFor.SSL,TrustedFor.SAML_ISSUER - - -Dcom.l7tech.server.audit.message.saveToInternal=false - - -Dcom.l7tech.server.audit.admin.saveToInternal=false - - -Dcom.l7tech.server.audit.system.saveToInternal=false - - -Dcom.l7tech.server.audit.log.format=json - - -Djava.util.logging.config.file=/opt/SecureSpan/Gateway/node/default/etc/conf/log-override.properties - - -Dcom.l7tech.server.pkix.useDefaultTrustAnchors=true - - -Dcom.l7tech.security.ssl.hostAllowWildcard=true - listenPorts: - harden: true - custom: - enabled: false - ports: [] - log: - override: true - properties: |- - handlers = com.l7tech.server.log.GatewayRootLoggingHandler, com.l7tech.server.log.ConsoleMessageSink$L7ConsoleHandler - com.l7tech.server.log.GatewayRootLoggingHandler.formatter = com.l7tech.util.JsonLogFormatter - java.util.logging.SimpleFormatter.format= - com.l7tech.server.log.ConsoleMessageSink$L7ConsoleHandler.formatter = com.l7tech.util.JsonLogFormatter - com.l7tech.server.log.ConsoleMessageSink$L7ConsoleHandler.level = CONFIG - cwp: - enabled: true - properties: - - name: io.httpsHostAllowWildcard - value: "true" - - name: log.levels - value: | - com.l7tech.level = CONFIG - com.l7tech.server.policy.variable.ServerVariables.level = SEVERE - com.l7tech.external.assertions.odata.server.producer.jdbc.GenerateSqlQuery.level = SEVERE - com.l7tech.server.policy.assertion.ServerSetVariableAssertion.level = SEVERE - com.l7tech.external.assertions.comparison.server.ServerComparisonAssertion.level = SEVERE - - name: audit.setDetailLevel.FINE - value: 152 7101 7103 9648 9645 7026 7027 4155 150 4716 4114 6306 4100 9655 150 151 11000 4104 - - name: otel.enabled - value: "true" - - name: otel.serviceMetricEnabled - value: "true" - - name: otel.traceEnabled - value: "true" - - name: otel.metricPrefix - value: l7_ - - name: otel.traceConfig - value: | - { - "services": [ - {"resolutionPath": ".*"} - ] - } - system: - properties: |- - # Default Gateway system properties - # Configuration properties for shared state extensions. - com.l7tech.server.extension.sharedKeyValueStoreProvider=embeddedhazelcast - com.l7tech.server.extension.sharedCounterProvider=ssgdb - com.l7tech.server.extension.sharedClusterInfoProvider=ssgdb - # By default, FIPS module will block an RSA modulus from being used for encryption if it has been used for - # signing, or visa-versa. Set true to disable this default behaviour and remain backwards compatible. - com.l7tech.org.bouncycastle.rsa.allow_multi_use=true - # Specifies the type of Trust Store (JKS/PKCS12) provided by AdoptOpenJDK that is used by Gateway. - # Must be set correctly when Gateway is running in FIPS mode. If not specified it will default to PKCS12. - javax.net.ssl.trustStoreType=jks - com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 - # OpenTelemetry Agent Configuration - otel.instrumentation.common.default-enabled=true - otel.instrumentation.opentelemetry-api.enabled=true - otel.instrumentation.runtime-metrics.enabled=true - otel.instrumentation.runtime-telemetry.enabled=true - otel.instrumentation.opentelemetry-instrumentation-annotations.enabled=true - otel.java.global-autoconfigure.enabled=true - # Additional properties go here - service: - # annotations: - type: LoadBalancer - ports: - - name: https - port: 8443 - targetPort: 8443 - protocol: TCP - - name: management - port: 9443 - targetPort: 9443 - protocol: TCP - - name: monitoring - port: 8889 - targetPort: 8889 - protocol: TCP - ingress: - enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" - tls: - - hosts: - - gateway.brcmlabs.com - secretName: default - rules: - - host: gateway.brcmlabs.com - # containerSecurityContext: - # runAsNonRoot: true - # runAsUser: 1000669998 - # capabilities: - # drop: - # - ALL - # allowPrivilegeEscalation: false - # podSecurityContext: - # runAsUser: 1000669998 - # runAsGroup: 1000669998 \ No newline at end of file diff --git a/example/gateway/otk/otk-dmz.yaml b/example/gateway/otk/otk-dmz.yaml index d87d8c52..c4abe46d 100644 --- a/example/gateway/otk/otk-dmz.yaml +++ b/example/gateway/otk/otk-dmz.yaml @@ -84,8 +84,8 @@ spec: # Management port requires a separate service... service: enabled: true - #annotations: - # cloud.google.com/load-balancer-type: "Internal" + annotations: + projectcontour.io/upstream-protocol.tls: "9443" type: LoadBalancer ports: - name: management @@ -155,7 +155,8 @@ spec: com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: ClusterIP ports: - name: https @@ -164,10 +165,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway-dmz.brcmlabs.com diff --git a/example/gateway/otk/otk-internal.yaml b/example/gateway/otk/otk-internal.yaml index d70af7e9..698e5350 100644 --- a/example/gateway/otk/otk-internal.yaml +++ b/example/gateway/otk/otk-internal.yaml @@ -149,7 +149,8 @@ spec: com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443,9443" type: ClusterIP ports: - name: https @@ -162,10 +163,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/otk/otk-single.yaml b/example/gateway/otk/otk-single.yaml index 641592fd..62dfcb04 100644 --- a/example/gateway/otk/otk-single.yaml +++ b/example/gateway/otk/otk-single.yaml @@ -77,33 +77,6 @@ spec: databaseWaitTimeout: 60 singletonExtraction: true - # livenessProbe: - # httpGet: - # port: https - # scheme: HTTPS - # path: /auth/oauth/health - # httpHeaders: - # - name: Host - # value: 127.0.0.1 - # failureThreshold: 25 - # initialDelaySeconds: 15 - # periodSeconds: 10 - # successThreshold: 1 - # timeoutSeconds: 1 - # readinessProbe: - # httpGet: - # port: https - # scheme: HTTPS - # path: /auth/oauth/health - # httpHeaders: - # - name: Host - # value: 127.0.0.1 - # failureThreshold: 25 - # initialDelaySeconds: 15 - # periodSeconds: 10 - # successThreshold: 1 - # timeoutSeconds: 1 - bundle: - type: restman source: secret @@ -155,6 +128,8 @@ spec: secretName: gateway-secret service: enabled: true + annotations: + projectcontour.io/upstream-protocol.tls: "9443" type: ClusterIP ports: - name: management @@ -172,7 +147,8 @@ spec: database: enabled: false service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: LoadBalancer ports: - name: https @@ -191,9 +167,8 @@ spec: # - host: gateway-otk-management.brcmlabs.com # port: # targetPort: management - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/gateway/otk/otk-ssg-dmz.yaml b/example/gateway/otk/otk-ssg-dmz.yaml index 967b2eed..3015705d 100644 --- a/example/gateway/otk/otk-ssg-dmz.yaml +++ b/example/gateway/otk/otk-ssg-dmz.yaml @@ -132,8 +132,8 @@ spec: # Management port requires a separate service... service: enabled: true - #annotations: - # cloud.google.com/load-balancer-type: "Internal" + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: LoadBalancer ports: - name: management @@ -203,7 +203,8 @@ spec: com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443" type: ClusterIP ports: - name: https @@ -212,10 +213,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - otk-ssg-dmz.brcmlabs.com diff --git a/example/gateway/otk/otk-ssg-internal.yaml b/example/gateway/otk/otk-ssg-internal.yaml index c0cd32dd..6cce28b6 100644 --- a/example/gateway/otk/otk-ssg-internal.yaml +++ b/example/gateway/otk/otk-ssg-internal.yaml @@ -196,7 +196,8 @@ spec: com.l7tech.server.clusterStaleNodeCleanupTimeoutSeconds=86400 # Additional properties go here service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443,9443" type: ClusterIP ports: - name: https @@ -209,10 +210,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - otk-ssg-internal.brcmlabs.com diff --git a/example/gateway/portal-gateway.yaml b/example/gateway/portal-gateway.yaml index d98c470b..63fec246 100644 --- a/example/gateway/portal-gateway.yaml +++ b/example/gateway/portal-gateway.yaml @@ -241,7 +241,8 @@ spec: com.l7tech.external.assertions.keyvaluestore.sharedKeyValueStoreProvider=redis com.l7tech.external.assertions.keyvaluestore.storeIdList=GW_STORE_ID service: - # annotations: + annotations: + projectcontour.io/upstream-protocol.tls: "8443,9443" type: ClusterIP ports: - name: https @@ -254,9 +255,8 @@ spec: protocol: TCP ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com diff --git a/example/images/elastic-assets.gif b/example/images/elastic-assets.gif deleted file mode 100644 index a51ccdfd04ad383a82b2bd1b380f7a5ccc91bbdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 672964 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

mDwj(1`pc1`0d>uLEGxR9lJjgKRpFc`jqd~& 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 z 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

?Nj>7Z1%h57IQ(%>g%yD9*_EFdY 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�NxBw^o8^)zes+|k|XcBs3w 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 IHPZ0;2j$);F8}}l diff --git a/example/otel-elastic/README.md b/example/otel-elastic/README.md index 7d744214..9fa27911 100644 --- a/example/otel-elastic/README.md +++ b/example/otel-elastic/README.md @@ -10,7 +10,7 @@ By the end of this example you should have a better understanding of the how to - Filebeat - MetricBeat - Kibana -- Nginx (Ingress Controller) +- [Contour](https://projectcontour.io/) (Ingress Controller) ### Elastic Stack Note This example creates a single [Elastic Search](./components/es.yaml) node with a 100GB volume. This is not production ready implementation, please refer to the [Official Elastic Documentation](https://www.elastic.co/guide/en/cloud-on-k8s/current/index.html) for sizing guidelines and additional configuration options. @@ -24,9 +24,10 @@ We make use of Filebeat for container log files (the Gateway + everything else) ![otel-eck-beats-example-overview](../images/beats.png) ## Prerequisites -- Kubernetes v1.25+ +- Kubernetes v1.34+ - Gateway v11.x License - Ingress Controller (for kibana) + - we have switched all examples from [Nginx](https://kubernetes.github.io/ingress-nginx/) to [Contour](https://projectcontour.io/) This OTel Elastic Example requires multiple namespaces for the additional components. Your Kubernetes user or service account must have sufficient privileges to create namespaces, deployments, configmaps, secrets, service accounts, roles, etc. @@ -105,15 +106,15 @@ If you have a docker machine available you can use [Kind](https://kind.sigs.k8s. ``` 3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - these will be referenced later on. -4. You will need an ingress controller like nginx +4. You will need an ingress controller like contour - if you do not have one installed already you can use the makefile in the example directory to deploy one - ```cd example``` - Generic Kubernetes - - ```make nginx``` + - ```make contour``` - Kind (Kubernetes in Docker) - follow the steps in Quickstart or - - ```make nginx-kind``` + - ```make contour-kind``` - return to the previous folder - ```cd ..``` @@ -133,9 +134,9 @@ If you use Quickstart you do not need to install/deploy any additional resources ### Monitoring/Observability Components - [Install Cert Manager](#install-cert-manager) - [Install Open Telemetry](#install-open-telemetry) -- [Install an Ingress Controller(optional)](#install-nginx) +- [Install an Ingress Controller(optional)](#install-contour) - [Install the Elastic Stack](#install-the-elastic-stack) -- [Install APM Components (Kibana UI)](#elastic-post-installation-tasks) +- [Deploy the Layer7 Dashboard (Kibana UI)](#elastic-post-installation-tasks) ### Layer7 Operator - [Deploy the Operator](#deploy-the-layer7-operator) @@ -354,29 +355,30 @@ This quickstart uses the Makefile to install of the necessary components into yo ``` cd example ``` -If you don't already have an ingress controller you can deploy nginx with the following command +If you don't already have an ingress controller you can deploy contour with the following command - Suppports most Kubernetes Environments ``` -make nginx +make contour ``` - If you're using Kind ``` -make nginx-kind +make contour-kind ``` - Additional options can be found here ``` -https://github.com/kubernetes/ingress-nginx/tree/main/deploy/static/provider +https://projectcontour.io/docs/1.33/ ``` -- Once nginx has been installed get the L4 LoadBalancer address +- Once contour has been installed get the L4 LoadBalancer address ``` -kubectl get svc -n ingress-nginx +kubectl get svc -n projectcontour ``` output ``` -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -ingress-nginx-controller LoadBalancer 10.152.183.52 192.168.1.190 80:30183/TCP,443:30886/TCP 24m -ingress-nginx-controller-admission ClusterIP 10.152.183.132 443/TCP 24m +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +contour ClusterIP 10.96.134.188 8001/TCP 29m +contour-envoy LoadBalancer 10.96.165.146 192.168.1.190 80:30377/TCP,443:31908/TCP 29m + ``` - You probably don't have access to a DNS server for this demo so you will need to add the following entries to your hosts file ``` @@ -483,20 +485,51 @@ NAME DESIRED replicaset.apps/opentelemetry-operator-controller-manager-5d84764d4b 1 1 1 72d ``` -### Install Nginx -This command will deploy an nginx ingress controller. If you already have nginx or another ingress controller running in your Kubernetes cluster you can safely ignore this step. +### Install Contour +This command will deploy the [contour](https://projectcontour.io/) ingress controller. If you already have contour or another ingress controller running in your Kubernetes cluster you can safely ignore this step. Suppports most Kubernetes Environments ``` -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml +helm repo add contour https://projectcontour.github.io/helm-charts/ +helm upgrade --install contour contour/contour --namespace projectcontour --create-namespace +kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=contour -n projectcontour +kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=envoy -n projectcontour ``` If you're using Kind ``` -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml +helm repo add contour https://projectcontour.github.io/helm-charts/ +helm upgrade --install contour contour/contour --namespace projectcontour --set envoy.useHostPort.http=true --set envoy.useHostPort.https=true --create-namespace +kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=contour -n projectcontour +kubectl wait --for=condition=ready --timeout=600s pod -l app.kubernetes.io/name=contour,app.kubernetes.io/component=envoy -n projectcontour ``` Additional options can be found here ``` -https://github.com/kubernetes/ingress-nginx/tree/main/deploy/static/provider +https://projectcontour.io/docs/1.33/ +``` + +#### Create a self-signed certificate for Contour to use +This is done automatically if you are using the quickstart commands via the Makefile. These steps can be simplified to the following +``` +make pki NAMESPACE=default +``` + +##### If you would like to run the steps manually do the following. + +- Set your namespace +``` +export NAMESPACE=default +``` +- Create self signed cert using openssl (this will be saved to your /tmp folder) +``` +openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj "/CN=*.brcmlabs.com" \ + -addext "subjectAltName=DNS:*.brcmlabs.com,DNS:brcmlabs.com" \ + -keyout /tmp/tls.key -out /tmp/tls.crt +``` +- Create a Kubernetes TLS secret +``` +kubectl create secret tls brcmlabs \ + --cert=/tmp/tls.crt --key=/tmp/tls.key \ + -n $NAMESPACE --dry-run=client -o yaml | kubectl apply -f - ``` ### Install the Elastic Stack @@ -504,8 +537,8 @@ These steps are based the [official documentation](https://www.elastic.co/guide/ #### Install Elastic Operator and Custom Resource Definitions ``` -kubectl create -f https://download.elastic.co/downloads/eck/2.8.0/crds.yaml -kubectl apply -f https://download.elastic.co/downloads/eck/2.8.0/operator.yaml +kubectl create -f https://download.elastic.co/downloads/eck/3.3.1/crds.yaml +kubectl apply -f https://download.elastic.co/downloads/eck/3.3.1/operator.yaml kubectl wait --for=condition=ready --timeout=60s pod -l control-plane=elastic-operator -n elastic-system ``` @@ -547,33 +580,16 @@ kubectl apply -f ./example/otel-elastic/instrumentation.yaml ``` #### Elastic Post Installation Tasks -The Layer7 Dashboard can be installed via the Kibana API, Elastic APM Components need to be manually installed via Kibana in a web browser. +The Layer7 Dashboard can be installed via the Kibana API. #### Create the Layer7 Dashboard Get the Elastic user password, you will use this password when logging into Kibana. ``` export elasticPass=$(kubectl get secret quickstart-es-elastic-user -o go-template='{{.data.elastic | base64decode}}') -curl -XPOST https://kibana.brcmlabs.com/api/saved_objects/_import?createNewCopies=false -H "kbn-xsrf: true" -k -uelastic:$elasticPass -F "file=@./example/otel-elastic/dashboard/apim-dashboard.ndjson" +curl -XPOST "https://kibana.brcmlabs.com/api/saved_objects/_import?createNewCopies=false" -H "kbn-xsrf: true" -k -uelastic:$elasticPass -F "file=@./example/otel-elastic/dashboard/apim-dashboard.ndjson" ``` -#### Install APM Components -There are additional APM Components that need to be installed via Kibana in a browser to see Gateway traces. - -- Navigate to the following [link](https://kibana.brcmlabs.com/app/integrations/detail/apm/overview) in a browser, you will need to accept the certificate warning and continue. -- Sign in - - username: elastic - - password: elastic-user-password - ``` - echo $elasticPass - ``` -- Click on the settings tab - - Install Elastic APM Assets - - Install Elastic APM - -![elastic-assets](../images/elastic-assets.gif) - - ### Deploy the Layer7 Operator This step will deploy the Layer7 Operator and all of its resources in namespaced mode. This means that it will only manage Gateway and Repository Custom Resources in the Kubernetes Namespace that it's deployed in. @@ -766,7 +782,7 @@ status: kubectl get ingress NAME CLASS HOSTS ADDRESS PORTS AGE -ssg nginx gateway.brcmlabs.com 34.89.126.80 80, 443 54m +ssg contour gateway.brcmlabs.com 34.89.126.80 80, 443 54m ``` Add the following to your hosts file for DNS resolution @@ -855,8 +871,8 @@ make uninstall-kind kubectl delete -f ./example/otel-elastic/components kubectl delete -f ./example/otel-elastic/collector.yaml kubectl delete -f ./example/otel-elastic/instrumentation.yaml -kubectl delete -f https://download.elastic.co/downloads/eck/2.8.0/crds.yaml -kubectl delete -f https://download.elastic.co/downloads/eck/2.8.0/operator.yaml +kubectl delete -f https://download.elastic.co/downloads/eck/3.3.1/crds.yaml +kubectl delete -f https://download.elastic.co/downloads/eck/3.3.1/operator.yaml kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml @@ -865,9 +881,9 @@ kubectl delete -f ./example/gateway/otel-elastic-gateway.yaml ``` -If you deployed Nginx manually +If you deployed Contour manually ``` -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml +helm uninstall contour -n projectcontour ``` ### Uninstall the Operator diff --git a/example/otel-elastic/collector.yaml b/example/otel-elastic/collector.yaml index 1888f9ad..c5bf04be 100644 --- a/example/otel-elastic/collector.yaml +++ b/example/otel-elastic/collector.yaml @@ -1,4 +1,4 @@ -apiVersion: opentelemetry.io/v1alpha1 +apiVersion: opentelemetry.io/v1beta1 kind: OpenTelemetryCollector metadata: name: ssg-eck @@ -14,7 +14,6 @@ spec: http: endpoint: 0.0.0.0:4318 processors: - batch: resource: attributes: - key: layer7gw.name @@ -30,20 +29,15 @@ spec: headers: Authorization: "Bearer APM_AUTH_TOKEN" service: - telemetry: - logs: - level: "debug" - metrics: - address: "0.0.0.0:8888" pipelines: traces: receivers: [otlp] - processors: [resource,batch] + processors: [resource] exporters: [otlp/elastic] metrics: receivers: [otlp] - processors: [resource,batch] - exporters: [otlp/elastic,debug] + processors: [resource] + exporters: [otlp/elastic] logs: receivers: [otlp] - exporters: [otlp/elastic, debug] \ No newline at end of file + exporters: [otlp/elastic] \ No newline at end of file diff --git a/example/otel-elastic/components/es.yaml b/example/otel-elastic/components/es.yaml index 5a5236e5..9bd9331f 100644 --- a/example/otel-elastic/components/es.yaml +++ b/example/otel-elastic/components/es.yaml @@ -38,12 +38,6 @@ spec: - -c - sysctl -w vm.max_map_count=262144 name: sysctl - # - name: install-plugins - # command: - # - sh - # - -c - # - | - # bin/elasticsearch-plugin install --batch elastic-apm resources: {} securityContext: privileged: true diff --git a/example/otel-elastic/components/ingress.yaml b/example/otel-elastic/components/ingress.yaml index 26115114..c25cb07d 100644 --- a/example/otel-elastic/components/ingress.yaml +++ b/example/otel-elastic/components/ingress.yaml @@ -1,11 +1,9 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTPS name: kibana spec: - ingressClassName: nginx + ingressClassName: contour rules: - host: kibana.brcmlabs.com http: @@ -20,4 +18,4 @@ spec: tls: - hosts: - kibana.brcmlabs.com - secretName: default \ No newline at end of file + secretName: brcmlabs \ No newline at end of file diff --git a/example/otel-elastic/components/kibana.yaml b/example/otel-elastic/components/kibana.yaml index 6077a77a..8c2daf12 100644 --- a/example/otel-elastic/components/kibana.yaml +++ b/example/otel-elastic/components/kibana.yaml @@ -9,5 +9,8 @@ spec: name: quickstart http: service: + metadata: + annotations: + projectcontour.io/upstream-protocol.tls: "5601" spec: type: ClusterIP # default is ClusterIP diff --git a/example/otel-elastic/components/metricbeat.yaml b/example/otel-elastic/components/metricbeat.yaml index b558d318..c3d7dcfb 100644 --- a/example/otel-elastic/components/metricbeat.yaml +++ b/example/otel-elastic/components/metricbeat.yaml @@ -73,7 +73,7 @@ spec: - -e - -c - /etc/beat.yml - - -system.hostfs=/hostfs + - --system.hostfs=/hostfs name: metricbeat volumeMounts: - mountPath: /hostfs/sys/fs/cgroup diff --git a/example/otel-elastic/dashboard/apim-dashboard.ndjson b/example/otel-elastic/dashboard/apim-dashboard.ndjson index 85cce660..7e361655 100644 --- a/example/otel-elastic/dashboard/apim-dashboard.ndjson +++ b/example/otel-elastic/dashboard/apim-dashboard.ndjson @@ -1,3 +1,3 @@ -{"attributes":{"allowNoIndex":true,"fieldAttrs":"{\"layer7_service_routing_latency\":{\"count\":5},\"broadcom_totalBackendLatency\":{\"count\":1},\"@timestamp\":{\"count\":1},\"layer7_totalBackendLatency\":{\"count\":2},\"layer7_totalBackendLatencyV1\":{\"count\":1},\"layer7_totalFrontendLatency\":{\"count\":1},\"db.client.connections.usage\":{\"count\":2},\"labels.pool_name\":{\"count\":1},\"labels.state\":{\"count\":1},\"process.runtime.jvm.classes.loaded\":{\"count\":2},\"db.client.connections.pending_requests\":{\"count\":1},\"labels.process_runtime_description\":{\"count\":2},\"process.runtime.jvm.gc.duration\":{\"count\":1},\"layer7_service_total\":{\"count\":1},\"process.runtime.jvm.memory.limit\":{\"count\":2},\"process.runtime.jvm.memory.usage\":{\"count\":1},\"process.runtime.jvm.system.cpu.load_1m\":{\"count\":1},\"layer7_routing_failues\":{\"count\":1},\"layer7_service_success\":{\"count\":1},\"layer7_service_latency\":{\"count\":2},\"labels.serviceName\":{\"count\":1},\"labels.gc\":{\"count\":1},\"labels.method\":{\"count\":1},\"numeric_labels.code\":{\"count\":1},\"process.runtime.jvm.threads.count\":{\"count\":1},\"span.name\":{\"count\":3},\"layer7_service_latency\":{\"count\":1},\"layer7_service_policy_violations\":{\"count\":1},\"labels.serviceName\":{\"count\":1},\"labels.layer7gw_name\":{\"count\":1}}","fieldFormatMap":"{\"trace.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/trace/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/transaction/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"showSuffix\":true,\"useShortSuffix\":true,\"outputPrecision\":2,\"includeSpaceWithSuffix\":true}}}","fields":"[]","name":"APM","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:23:11.980Z","id":"apm_static_index_pattern_id","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-14T07:23:11.980Z","version":"WzQyMiwxXQ=="} -{"attributes":{"controlGroupInput":{"chainingSystem":"HIERARCHICAL","controlStyle":"twoLine","ignoreParentSettingsJSON":"{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}","panelsJSON":"{\"d3e0d4d0-b1de-465c-8772-806a49213c2b\":{\"type\":\"optionsListControl\",\"order\":0,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"id\":\"d3e0d4d0-b1de-465c-8772-806a49213c2b\",\"fieldName\":\"labels.layer7gw_name\",\"title\":\"Gateway\",\"selectedOptions\":[],\"enhancements\":{},\"existsSelected\":false}}}"},"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":22,\"h\":11,\"i\":\"675c235a-535c-48d9-a5ed-848810986e5a\"},\"panelIndex\":\"675c235a-535c-48d9-a5ed-848810986e5a\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\",\"isTransposed\":false,\"summaryRow\":\"sum\"},{\"columnId\":\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ff0032fe-94ec-4bf8-a50f-9905301868f9\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"isTransposed\":false}],\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"layerType\":\"data\",\"paging\":{\"size\":10,\"enabled\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"ed1df4b3-0bcd-4059-965d-39bc63eef215\":{\"label\":\"Service Name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]},\"customLabel\":true},\"82dab02b-8829-488e-bd30-aa8c45996590\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_policy_violations\",\"filter\":{\"query\":\"layer7_service_policy_violations: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_success\",\"filter\":{\"query\":\"layer7_service_success: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ff0032fe-94ec-4bf8-a50f-9905301868f9\":{\"label\":\"Routing Failures\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_routing_failures\",\"filter\":{\"query\":\"layer7_service_routing_failures: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"82dab02b-8829-488e-bd30-aa8c45996590\",\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"ff0032fe-94ec-4bf8-a50f-9905301868f9\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":0,\"w\":26,\"h\":11,\"i\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\"},\"panelIndex\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"accessors\":[\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\"],\"position\":\"top\",\"seriesType\":\"line\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"splitAccessor\":\"2bffabd5-d07e-4187-a67d-c1d085ea598a\"}],\"curveType\":\"CURVE_MONOTONE_X\",\"fittingFunction\":\"Zero\",\"emphasizeFitting\":true,\"endValue\":\"Zero\",\"valuesInLegend\":false,\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"yTitle\":\"Requests\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"cad7aae7-308f-4c4f-abbd-b03792fbec56\":{\"columns\":{\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"2bffabd5-d07e-4187-a67d-c1d085ea598a\":{\"label\":\"Top 10 values of labels.serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}},\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\":{\"label\":\"Requests\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"82148cde-9560-4544-a841-38ccaf7b66f5\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"2bffabd5-d07e-4187-a67d-c1d085ea598a\",\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\",\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Message Rate\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":11,\"w\":22,\"h\":11,\"i\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\"},\"panelIndex\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Latency (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"fd66544d-5ee7-4f36-8966-c7aa38197646\",\"accessors\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"splitAccessor\":\"4ef8265b-6ee1-449f-9ced-ae065efc7296\"}],\"showCurrentTimeMarker\":false,\"yLeftScale\":\"linear\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"fd66544d-5ee7-4f36-8966-c7aa38197646\":{\"columns\":{\"c973fd1a-9645-46a1-ad45-483cf1ff0441\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"subtract\",\"args\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"location\":{\"min\":0,\"max\":63},\"text\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\"}},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\":{\"label\":\"Frontend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\"],\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\":{\"label\":\"Part of Backend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\":{\"label\":\"Backend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"customLabel\":true},\"4ef8265b-6ee1-449f-9ced-ae065efc7296\":{\"label\":\"Top 10 values of labels.serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}}},\"columnOrder\":[\"4ef8265b-6ee1-449f-9ced-ae065efc7296\",\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Latency\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":11,\"w\":26,\"h\":11,\"i\":\"d9305573-c761-4802-9c0b-6f6326266125\"},\"panelIndex\":\"d9305573-c761-4802-9c0b-6f6326266125\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Number of Messages\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"seriesType\":\"bar\",\"accessors\":[\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"231179ca-b88a-4495-b857-8006bca2b409\"],\"layerType\":\"data\",\"yConfig\":[{\"forAccessor\":\"231179ca-b88a-4495-b857-8006bca2b409\",\"color\":\"#8b8c2b\",\"axisMode\":\"left\"},{\"forAccessor\":\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"color\":\"#2da275\",\"axisMode\":\"left\"},{\"forAccessor\":\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"color\":\"#781032\",\"axisMode\":\"left\"}],\"xAccessor\":\"227364f6-80d8-4d15-89ca-7166dc900804\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"227364f6-80d8-4d15-89ca-7166dc900804\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"m\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"231179ca-b88a-4495-b857-8006bca2b409\":{\"label\":\"Routing failues\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"6e76dd16-427c-4267-a977-522b4f1a8eba\"],\"customLabel\":true,\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}}},\"6e76dd16-427c-4267-a977-522b4f1a8eba\":{\"label\":\"Maximum of layer7_service_routing_failures\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_routing_failures\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"84104e54-bd82-47f6-b34d-b04a1db60694\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"84104e54-bd82-47f6-b34d-b04a1db60694\":{\"label\":\"Maximum of layer7_service_policy_violations\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_policy_violations\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"28a49db2-d0b2-47ea-bd58-12e827e9851d\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"3da518c4-50c1-415f-8248-b11b5be8873f\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"227364f6-80d8-4d15-89ca-7166dc900804\",\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"231179ca-b88a-4495-b857-8006bca2b409\",\"6e76dd16-427c-4267-a977-522b4f1a8eba\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"84104e54-bd82-47f6-b34d-b04a1db60694\",\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Service Status\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":22,\"w\":48,\"h\":15,\"i\":\"868932e9-487a-408f-89fd-14f9324534e9\"},\"panelIndex\":\"868932e9-487a-408f-89fd-14f9324534e9\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\",\"isInside\":false,\"verticalAlignment\":\"bottom\",\"horizontalAlignment\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"Zero\",\"curveType\":\"CURVE_MONOTONE_X\",\"yLeftScale\":\"sqrt\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar\",\"layers\":[{\"layerId\":\"2381ace6-24bb-4e47-b57b-6fe06c980216\",\"accessors\":[\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"splitAccessor\":\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"collapseFn\":\"\"},{\"layerId\":\"06696642-3076-4612-8291-189dcb25dd9e\",\"layerType\":\"data\",\"accessors\":[\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"seriesType\":\"line\",\"xAccessor\":\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"splitAccessor\":\"8756242d-1a48-4550-9fd5-66057b194995\"}],\"emphasizeFitting\":true,\"yTitle\":\"Connections\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"2381ace6-24bb-4e47-b57b-6fe06c980216\":{\"columns\":{\"b8a1138e-65ed-4595-8c72-23c294313fc5\":{\"label\":\"Top values of labels.pool_name + 1 other\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"b13dba51-c5c9-4c20-9212-e8607d048660\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"multi_terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[\"labels.state\"]}},\"209b6a96-6641-42d5-b243-cd7a68dc55be\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"b13dba51-c5c9-4c20-9212-e8607d048660\":{\"label\":\"conns\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.usage\",\"filter\":{\"query\":\"db.client.connections.usage: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"incompleteColumns\":{},\"sampling\":1},\"06696642-3076-4612-8291-189dcb25dd9e\":{\"linkToLayers\":[],\"columns\":{\"74470d07-35e7-40c2-bf78-7bd282323271\":{\"label\":\"pending requests\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.pending_requests\",\"filter\":{\"query\":\"db.client.connections.pending_requests: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"96a4345f-06e4-4252-9ed1-31ed39ee347c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"8756242d-1a48-4550-9fd5-66057b194995\":{\"label\":\"Top 3 values of labels.pool_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"74470d07-35e7-40c2-bf78-7bd282323271\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"8756242d-1a48-4550-9fd5-66057b194995\",\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"DB Connections\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":37,\"w\":22,\"h\":10,\"i\":\"9a54b939-5371-49f6-8888-ab92e165e625\"},\"panelIndex\":\"9a54b939-5371-49f6-8888-ab92e165e625\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"accessors\":[\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"yConfig\":[{\"forAccessor\":\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"color\":\"#e7664c\"}]}],\"yTitle\":\"CPU utilization (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\":{\"columns\":{\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f0c37a83-1464-4350-bb91-e57c2198b927X0\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927X1\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"multiply\",\"args\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",100],\"location\":{\"min\":0,\"max\":121},\"text\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\"}},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\"],\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927\":{\"label\":\"System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\",\"isFormulaBroken\":false},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X1\"],\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\":{\"label\":\"Part of JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149\":{\"label\":\"JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.cpu.utilization, kql='process.runtime.jvm.cpu.utilization: *')\",\"isFormulaBroken\":false},\"references\":[\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"customLabel\":true}},\"columnOrder\":[\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",\"f0c37a83-1464-4350-bb91-e57c2198b927X1\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"CPU/System Utilization\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":37,\"w\":26,\"h\":10,\"i\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\"},\"panelIndex\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"accessors\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"splitAccessor\":\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"palette\":{\"type\":\"palette\",\"name\":\"status\"},\"collapseFn\":\"\",\"yConfig\":[]},{\"layerId\":\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"layerType\":\"referenceLine\",\"accessors\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\"],\"yConfig\":[{\"forAccessor\":\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"axisMode\":\"left\",\"icon\":\"bolt\",\"iconPosition\":\"auto\",\"lineWidth\":2,\"color\":\"#9d2022\"}]},{\"layerId\":\"66ce3428-e822-4b54-afc8-69bbffd9c106\",\"layerType\":\"data\",\"accessors\":[\"323e403c-e4a1-481d-897e-907192f1cc4a\"],\"seriesType\":\"line\",\"xAccessor\":\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"yConfig\":[{\"forAccessor\":\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"color\":\"#040f12\"}]}],\"yLeftExtent\":{\"mode\":\"full\"},\"yTitle\":\"Megabytes\",\"yLeftScale\":\"log\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\":{\"columns\":{\"7e7413d7-db3d-4473-b46f-d76132f403b0\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\"}},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\"],\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\":{\"label\":\"Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"customLabel\":true},\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"accuracyMode\":true}}},\"columnOrder\":[\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"incompleteColumns\":{},\"sampling\":1},\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\":{\"linkToLayers\":[],\"columns\":{\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.limit\",\"filter\":{\"query\":\"process.runtime.jvm.memory.limit: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\"}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\"],\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81\":{\"label\":\"Max\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false,\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"customLabel\":true}},\"columnOrder\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"sampling\":1,\"incompleteColumns\":{}},\"66ce3428-e822-4b54-afc8-69bbffd9c106\":{\"linkToLayers\":[],\"columns\":{\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"323e403c-e4a1-481d-897e-907192f1cc4aX0\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: non_heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4aX1\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\",1048576],\"location\":{\"min\":0,\"max\":130},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\"}},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4a\":{\"label\":\"Non-Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX1\"],\"customLabel\":true}},\"columnOrder\":[\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"323e403c-e4a1-481d-897e-907192f1cc4aX1\",\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"enhancements\":{},\"hidePanelTitles\":false},\"title\":\"Memory Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":47,\"w\":22,\"h\":11,\"i\":\"542275ac-682e-43b8-b1ac-cf919d206e02\"},\"panelIndex\":\"542275ac-682e-43b8-b1ac-cf919d206e02\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"accessors\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241b\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\"}],\"yTitle\":\"System Load (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"28f4234a-7411-4d15-8ad9-36ac978f7e19\":{\"columns\":{\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\":{\"label\":\"Part of System Load\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.load_1m\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.load_1m: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"04481efd-b3d8-44bc-be6c-5a0af56e241b\":{\"label\":\"System Load\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.system.cpu.load_1m, kql='process.runtime.jvm.system.cpu.load_1m: *')\",\"isFormulaBroken\":false},\"references\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"customLabel\":true}},\"columnOrder\":[\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\",\"04481efd-b3d8-44bc-be6c-5a0af56e241b\",\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"System Load\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":47,\"w\":26,\"h\":11,\"i\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\"},\"panelIndex\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"accessors\":[\"603e1430-a496-4249-b2da-73a5205b964e\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"splitAccessor\":\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"yConfig\":[{\"forAccessor\":\"603e1430-a496-4249-b2da-73a5205b964e\",\"axisMode\":\"left\"}],\"palette\":{\"type\":\"palette\",\"name\":\"status\"}}],\"yLeftScale\":\"log\",\"yTitle\":\"Megabytes\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\":{\"columns\":{\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"603e1430-a496-4249-b2da-73a5205b964eX0\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage_after_last_gc\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964eX1\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\",1048576],\"location\":{\"min\":0,\"max\":154},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\"}},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\"],\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964e\":{\"label\":\"MB\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"customLabel\":true},\"a9ec0126-a45f-4f86-b783-a4820badd7d8\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"603e1430-a496-4249-b2da-73a5205b964e\",\"603e1430-a496-4249-b2da-73a5205b964eX0\",\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Memory after GC\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":58,\"w\":22,\"h\":10,\"i\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\"},\"panelIndex\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"accessors\":[\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"9c350955-95a1-4726-93fd-717b104a218c\",\"yConfig\":[{\"forAccessor\":\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\",\"color\":\"#54b399\"}]}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a3ee7019-98bb-45cd-8536-e36a8e58297e\":{\"columns\":{\"9c350955-95a1-4726-93fd-717b104a218c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\":{\"label\":\"Threads count\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.threads.count\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"process.runtime.jvm.threads.count: *\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9c350955-95a1-4726-93fd-717b104a218c\",\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Threads\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":58,\"w\":26,\"h\":10,\"i\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\"},\"panelIndex\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yRightTitle\":\"Duration (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"accessors\":[\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"yConfig\":[{\"forAccessor\":\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"axisMode\":\"left\"},{\"forAccessor\":\"4e7a6993-fcee-47ae-9599-0047809bb29e\",\"color\":\"#e7664c\",\"axisMode\":\"left\"}]}],\"yTitle\":\"Duration (ms)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a45cd02a-4c62-4b34-bdce-c702b87b4246\":{\"columns\":{\"57050bde-2c14-47d2-9dde-6e35326cf5cf\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\":{\"label\":\"Minor GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action: \\\"end of minor GC\\\" \",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":2}},\"emptyAsNull\":true},\"customLabel\":true},\"4e7a6993-fcee-47ae-9599-0047809bb29e\":{\"label\":\"Major GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action : \\\"end of major GC\\\"\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Garbage Collection\"}]","timeRestore":false,"title":"Layer7 Gateway Dashboard","version":1},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:28:39.093Z","id":"c29a9900-2161-11ee-be6c-57cbef857d54","managed":false,"references":[{"id":"apm_static_index_pattern_id","name":"675c235a-535c-48d9-a5ed-848810986e5a:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"0ae50fd8-f924-4207-b546-5d600ac701b3:indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7:indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"d9305573-c761-4802-9c0b-6f6326266125:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"9a54b939-5371-49f6-8888-ab92e165e625:indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"542275ac-682e-43b8-b1ac-cf919d206e02:indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"a3aace75-debc-4370-9954-e5a6e8ac6259:indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367:indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"4f987373-1b85-4e80-a7cc-c1c000349c5e:indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"controlGroup_d3e0d4d0-b1de-465c-8772-806a49213c2b:optionsListDataView","type":"index-pattern"}],"type":"dashboard","typeMigrationVersion":"8.7.0","updated_at":"2024-05-14T07:28:39.093Z","version":"WzE2ODksMV0="} +{"attributes":{"allowNoIndex":true,"fieldAttrs":"{\"layer7_service_routing_latency\":{\"count\":5},\"broadcom_totalBackendLatency\":{\"count\":1},\"@timestamp\":{\"count\":1},\"layer7_totalBackendLatency\":{\"count\":2},\"layer7_totalBackendLatencyV1\":{\"count\":1},\"layer7_totalFrontendLatency\":{\"count\":1},\"db.client.connections.usage\":{\"count\":2},\"labels.pool_name\":{\"count\":1},\"labels.state\":{\"count\":1},\"process.runtime.jvm.classes.loaded\":{\"count\":2},\"db.client.connections.pending_requests\":{\"count\":1},\"labels.process_runtime_description\":{\"count\":2},\"process.runtime.jvm.gc.duration\":{\"count\":1},\"layer7_service_total\":{\"count\":1},\"process.runtime.jvm.memory.limit\":{\"count\":2},\"process.runtime.jvm.memory.usage\":{\"count\":1},\"process.runtime.jvm.system.cpu.load_1m\":{\"count\":1},\"layer7_routing_failues\":{\"count\":1},\"layer7_service_success\":{\"count\":1},\"layer7_service_latency\":{\"count\":2},\"labels.serviceName\":{\"count\":1},\"labels.gc\":{\"count\":1},\"labels.method\":{\"count\":1},\"numeric_labels.code\":{\"count\":1},\"process.runtime.jvm.threads.count\":{\"count\":1},\"span.name\":{\"count\":3},\"layer7_service_latency\":{\"count\":1},\"layer7_service_policy_violations\":{\"count\":1},\"labels.layer7_serviceName\":{\"count\":1},\"labels.layer7gw_name\":{\"count\":1}}","fieldFormatMap":"{\"trace.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/trace/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"apm/link-to/transaction/{{value}}\",\"labelTemplate\":\"{{value}}\"}},\"transaction.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"showSuffix\":true,\"useShortSuffix\":true,\"outputPrecision\":2,\"includeSpaceWithSuffix\":true}}}","fields":"[]","name":"APM","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*","typeMeta":"{}"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:23:11.980Z","id":"apm_static_index_pattern_id","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-14T07:23:11.980Z","version":"WzQyMiwxXQ=="} +{"attributes":{"controlGroupInput":{"chainingSystem":"HIERARCHICAL","controlStyle":"twoLine","ignoreParentSettingsJSON":"{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}","panelsJSON":"{\"d3e0d4d0-b1de-465c-8772-806a49213c2b\":{\"type\":\"optionsListControl\",\"order\":0,\"grow\":true,\"width\":\"medium\",\"explicitInput\":{\"id\":\"d3e0d4d0-b1de-465c-8772-806a49213c2b\",\"fieldName\":\"labels.layer7gw_name\",\"title\":\"Gateway\",\"selectedOptions\":[],\"enhancements\":{},\"existsSelected\":false}}}"},"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":22,\"h\":11,\"i\":\"675c235a-535c-48d9-a5ed-848810986e5a\"},\"panelIndex\":\"675c235a-535c-48d9-a5ed-848810986e5a\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\"}],\"state\":{\"visualization\":{\"columns\":[{\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\",\"isTransposed\":false,\"summaryRow\":\"sum\"},{\"columnId\":\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ff0032fe-94ec-4bf8-a50f-9905301868f9\",\"isTransposed\":false,\"summaryRow\":\"sum\",\"summaryLabel\":\"\"},{\"columnId\":\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"isTransposed\":false}],\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"layerType\":\"data\",\"paging\":{\"size\":10,\"enabled\":true}},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"ed1df4b3-0bcd-4059-965d-39bc63eef215\":{\"label\":\"Service Name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.layer7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"column\",\"columnId\":\"82dab02b-8829-488e-bd30-aa8c45996590\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]},\"customLabel\":true},\"82dab02b-8829-488e-bd30-aa8c45996590\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_policy_violations\",\"filter\":{\"query\":\"layer7_service_policy_violations: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_success\",\"filter\":{\"query\":\"layer7_service_success: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"ff0032fe-94ec-4bf8-a50f-9905301868f9\":{\"label\":\"Routing Failures\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"layer7_service_routing_failures\",\"filter\":{\"query\":\"layer7_service_routing_failures: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"ed1df4b3-0bcd-4059-965d-39bc63eef215\",\"82dab02b-8829-488e-bd30-aa8c45996590\",\"ae1e2f3d-6ec8-49c9-a444-8249ed271df8\",\"ff0032fe-94ec-4bf8-a50f-9905301868f9\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":0,\"w\":26,\"h\":11,\"i\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\"},\"panelIndex\":\"0ae50fd8-f924-4207-b546-5d600ac701b3\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"title\":\"Empty XY chart\",\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"cad7aae7-308f-4c4f-abbd-b03792fbec56\",\"accessors\":[\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\"],\"position\":\"top\",\"seriesType\":\"line\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"splitAccessor\":\"2bffabd5-d07e-4187-a67d-c1d085ea598a\"}],\"curveType\":\"CURVE_MONOTONE_X\",\"fittingFunction\":\"Zero\",\"emphasizeFitting\":true,\"endValue\":\"Zero\",\"valuesInLegend\":false,\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"yTitle\":\"Requests\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"cad7aae7-308f-4c4f-abbd-b03792fbec56\":{\"columns\":{\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"2bffabd5-d07e-4187-a67d-c1d085ea598a\":{\"label\":\"Top 10 values of labels.layer7_serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.layer7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}},\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\":{\"label\":\"Requests\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"82148cde-9560-4544-a841-38ccaf7b66f5\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"2bffabd5-d07e-4187-a67d-c1d085ea598a\",\"bf646547-1ad3-4810-8d0c-ac6d35be61ec\",\"4ee0c11f-c34e-48b9-ba2b-3a855498cd44\",\"82148cde-9560-4544-a841-38ccaf7b66f5\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Message Rate\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":11,\"w\":22,\"h\":11,\"i\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\"},\"panelIndex\":\"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Latency (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"fd66544d-5ee7-4f36-8966-c7aa38197646\",\"accessors\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"splitAccessor\":\"4ef8265b-6ee1-449f-9ced-ae065efc7296\"}],\"showCurrentTimeMarker\":false,\"yLeftScale\":\"linear\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"fd66544d-5ee7-4f36-8966-c7aa38197646\":{\"columns\":{\"c973fd1a-9645-46a1-ad45-483cf1ff0441\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\":{\"label\":\"Part of Frontend\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"subtract\",\"args\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"location\":{\"min\":0,\"max\":63},\"text\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\"}},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\"],\"customLabel\":true},\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\":{\"label\":\"Frontend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_latency)-average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\"],\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\":{\"label\":\"Part of Backend\",\"dataType\":\"number\",\"operationType\":\"average\",\"sourceField\":\"layer7_service_routing_latency\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":false},\"customLabel\":true},\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\":{\"label\":\"Backend\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"average(layer7_service_routing_latency)\",\"isFormulaBroken\":false},\"references\":[\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"customLabel\":true},\"4ef8265b-6ee1-449f-9ced-ae065efc7296\":{\"label\":\"Top 10 values of labels.layer7_serviceName\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.layer7_serviceName\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[]}}},\"columnOrder\":[\"4ef8265b-6ee1-449f-9ced-ae065efc7296\",\"c973fd1a-9645-46a1-ad45-483cf1ff0441\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4b\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X0\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X2\",\"f4fc3664-ef1d-44f5-b9ce-edb2168d5ce5X1\",\"6da1dadd-1f1a-42d6-b954-e8bd5a318f4bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Latency\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":11,\"w\":26,\"h\":11,\"i\":\"d9305573-c761-4802-9c0b-6f6326266125\"},\"panelIndex\":\"d9305573-c761-4802-9c0b-6f6326266125\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yTitle\":\"Number of Messages\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\",\"seriesType\":\"bar\",\"accessors\":[\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"231179ca-b88a-4495-b857-8006bca2b409\"],\"layerType\":\"data\",\"yConfig\":[{\"forAccessor\":\"231179ca-b88a-4495-b857-8006bca2b409\",\"color\":\"#8b8c2b\",\"axisMode\":\"left\"},{\"forAccessor\":\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"color\":\"#2da275\",\"axisMode\":\"left\"},{\"forAccessor\":\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"color\":\"#781032\",\"axisMode\":\"left\"}],\"xAccessor\":\"227364f6-80d8-4d15-89ca-7166dc900804\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"8638d8b0-f850-42f0-9e64-35e6f239e0d0\":{\"columns\":{\"227364f6-80d8-4d15-89ca-7166dc900804\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"m\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"231179ca-b88a-4495-b857-8006bca2b409\":{\"label\":\"Routing failues\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"6e76dd16-427c-4267-a977-522b4f1a8eba\"],\"customLabel\":true,\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}}},\"6e76dd16-427c-4267-a977-522b4f1a8eba\":{\"label\":\"Maximum of layer7_service_routing_failures\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_routing_failures\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\":{\"label\":\"Policy Violations\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"84104e54-bd82-47f6-b34d-b04a1db60694\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"84104e54-bd82-47f6-b34d-b04a1db60694\":{\"label\":\"Maximum of layer7_service_policy_violations\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_policy_violations\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}},\"28a49db2-d0b2-47ea-bd58-12e827e9851d\":{\"label\":\"Success\",\"dataType\":\"number\",\"operationType\":\"counter_rate\",\"isBucketed\":false,\"scale\":\"ratio\",\"references\":[\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"timeScale\":\"s\",\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"customLabel\":true},\"3da518c4-50c1-415f-8248-b11b5be8873f\":{\"label\":\"Maximum of layer7_service_attempted\",\"dataType\":\"number\",\"operationType\":\"max\",\"sourceField\":\"layer7_service_attempted\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"emptyAsNull\":true}}},\"columnOrder\":[\"227364f6-80d8-4d15-89ca-7166dc900804\",\"28a49db2-d0b2-47ea-bd58-12e827e9851d\",\"231179ca-b88a-4495-b857-8006bca2b409\",\"6e76dd16-427c-4267-a977-522b4f1a8eba\",\"6d6fe07e-c712-4d47-89c6-1ebdeaa23304\",\"84104e54-bd82-47f6-b34d-b04a1db60694\",\"3da518c4-50c1-415f-8248-b11b5be8873f\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Service Status\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":22,\"w\":48,\"h\":15,\"i\":\"868932e9-487a-408f-89fd-14f9324534e9\"},\"panelIndex\":\"868932e9-487a-408f-89fd-14f9324534e9\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\",\"isInside\":false,\"verticalAlignment\":\"bottom\",\"horizontalAlignment\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"Zero\",\"curveType\":\"CURVE_MONOTONE_X\",\"yLeftScale\":\"sqrt\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar\",\"layers\":[{\"layerId\":\"2381ace6-24bb-4e47-b57b-6fe06c980216\",\"accessors\":[\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"splitAccessor\":\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"collapseFn\":\"\"},{\"layerId\":\"06696642-3076-4612-8291-189dcb25dd9e\",\"layerType\":\"data\",\"accessors\":[\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"seriesType\":\"line\",\"xAccessor\":\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"splitAccessor\":\"8756242d-1a48-4550-9fd5-66057b194995\"}],\"emphasizeFitting\":true,\"yTitle\":\"Connections\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"2381ace6-24bb-4e47-b57b-6fe06c980216\":{\"columns\":{\"b8a1138e-65ed-4595-8c72-23c294313fc5\":{\"label\":\"Top values of labels.pool_name + 1 other\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"b13dba51-c5c9-4c20-9212-e8607d048660\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"multi_terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"secondaryFields\":[\"labels.state\"]}},\"209b6a96-6641-42d5-b243-cd7a68dc55be\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"b13dba51-c5c9-4c20-9212-e8607d048660\":{\"label\":\"conns\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.usage\",\"filter\":{\"query\":\"db.client.connections.usage: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true}},\"columnOrder\":[\"b8a1138e-65ed-4595-8c72-23c294313fc5\",\"209b6a96-6641-42d5-b243-cd7a68dc55be\",\"b13dba51-c5c9-4c20-9212-e8607d048660\"],\"incompleteColumns\":{},\"sampling\":1},\"06696642-3076-4612-8291-189dcb25dd9e\":{\"linkToLayers\":[],\"columns\":{\"74470d07-35e7-40c2-bf78-7bd282323271\":{\"label\":\"pending requests\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"db.client.connections.pending_requests\",\"filter\":{\"query\":\"db.client.connections.pending_requests: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"96a4345f-06e4-4252-9ed1-31ed39ee347c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"8756242d-1a48-4550-9fd5-66057b194995\":{\"label\":\"Top 3 values of labels.pool_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool_name\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"74470d07-35e7-40c2-bf78-7bd282323271\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"8756242d-1a48-4550-9fd5-66057b194995\",\"96a4345f-06e4-4252-9ed1-31ed39ee347c\",\"74470d07-35e7-40c2-bf78-7bd282323271\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"DB Connections\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":37,\"w\":22,\"h\":10,\"i\":\"9a54b939-5371-49f6-8888-ab92e165e625\"},\"panelIndex\":\"9a54b939-5371-49f6-8888-ab92e165e625\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\",\"accessors\":[\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"yConfig\":[{\"forAccessor\":\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"color\":\"#e7664c\"}]}],\"yTitle\":\"CPU utilization (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a57f2de0-c4c8-4a18-9b3a-533ad5572097\":{\"columns\":{\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"f0c37a83-1464-4350-bb91-e57c2198b927X0\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927X1\":{\"label\":\"Part of System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"multiply\",\"args\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",100],\"location\":{\"min\":0,\"max\":121},\"text\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\"}},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X0\"],\"customLabel\":true},\"f0c37a83-1464-4350-bb91-e57c2198b927\":{\"label\":\"System CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"multiply(last_value(process.runtime.jvm.system.cpu.utilization, kql='process.runtime.jvm.system.cpu.utilization: *'),100)\",\"isFormulaBroken\":false},\"references\":[\"f0c37a83-1464-4350-bb91-e57c2198b927X1\"],\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\":{\"label\":\"Part of JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.cpu.utilization\",\"filter\":{\"query\":\"process.runtime.jvm.cpu.utilization: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"cdcae1da-c18f-40a5-aeed-1be00a243149\":{\"label\":\"JVM CPU utilization\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.cpu.utilization, kql='process.runtime.jvm.cpu.utilization: *')\",\"isFormulaBroken\":false},\"references\":[\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"customLabel\":true}},\"columnOrder\":[\"deb7bb17-0f00-4a18-b2be-e6a5a2becb7e\",\"f0c37a83-1464-4350-bb91-e57c2198b927\",\"f0c37a83-1464-4350-bb91-e57c2198b927X0\",\"f0c37a83-1464-4350-bb91-e57c2198b927X1\",\"cdcae1da-c18f-40a5-aeed-1be00a243149\",\"cdcae1da-c18f-40a5-aeed-1be00a243149X0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"CPU/System Utilization\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":37,\"w\":26,\"h\":10,\"i\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\"},\"panelIndex\":\"2a2057e2-ce45-4804-8ce0-3d4ed94679d7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"type\":\"index-pattern\"},{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\",\"accessors\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"splitAccessor\":\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"palette\":{\"type\":\"palette\",\"name\":\"status\"},\"collapseFn\":\"\",\"yConfig\":[]},{\"layerId\":\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\",\"layerType\":\"referenceLine\",\"accessors\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\"],\"yConfig\":[{\"forAccessor\":\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"axisMode\":\"left\",\"icon\":\"bolt\",\"iconPosition\":\"auto\",\"lineWidth\":2,\"color\":\"#9d2022\"}]},{\"layerId\":\"66ce3428-e822-4b54-afc8-69bbffd9c106\",\"layerType\":\"data\",\"accessors\":[\"323e403c-e4a1-481d-897e-907192f1cc4a\"],\"seriesType\":\"line\",\"xAccessor\":\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"yConfig\":[{\"forAccessor\":\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"color\":\"#040f12\"}]}],\"yLeftExtent\":{\"mode\":\"full\"},\"yTitle\":\"Megabytes\",\"yLeftScale\":\"log\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"04cd0fc2-f652-4583-a7d4-c1a8f6f8818f\":{\"columns\":{\"7e7413d7-db3d-4473-b46f-d76132f403b0\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\":{\"label\":\"Part of Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\"}},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\"],\"customLabel\":true},\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\":{\"label\":\"Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"customLabel\":true},\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false,\"accuracyMode\":true}}},\"columnOrder\":[\"06b7cd00-d4bf-4c98-a2e1-2932a19a92ae\",\"7e7413d7-db3d-4473-b46f-d76132f403b0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X0\",\"371f4c5e-dca1-4de3-b495-b29d0c51ad42X1\"],\"incompleteColumns\":{},\"sampling\":1},\"b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d\":{\"linkToLayers\":[],\"columns\":{\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.limit\",\"filter\":{\"query\":\"process.runtime.jvm.memory.limit: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\":{\"label\":\"Part of Max\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",1048576],\"location\":{\"min\":0,\"max\":126},\"text\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\"}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\"],\"customLabel\":true},\"81fcf196-8145-4246-9d3a-f1679cb62c81\":{\"label\":\"Max\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.limit, kql='process.runtime.jvm.memory.limit: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false,\"format\":{\"id\":\"number\",\"params\":{\"decimals\":0}}},\"references\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"customLabel\":true}},\"columnOrder\":[\"81fcf196-8145-4246-9d3a-f1679cb62c81\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X0\",\"81fcf196-8145-4246-9d3a-f1679cb62c81X1\"],\"sampling\":1,\"incompleteColumns\":{}},\"66ce3428-e822-4b54-afc8-69bbffd9c106\":{\"linkToLayers\":[],\"columns\":{\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"323e403c-e4a1-481d-897e-907192f1cc4aX0\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage: * and labels.type: non_heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4aX1\":{\"label\":\"Part of Non-Heap\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\",1048576],\"location\":{\"min\":0,\"max\":130},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\"}},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"customLabel\":true},\"323e403c-e4a1-481d-897e-907192f1cc4a\":{\"label\":\"Non-Heap\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage, kql='process.runtime.jvm.memory.usage: * and labels.type: non_heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"323e403c-e4a1-481d-897e-907192f1cc4aX1\"],\"customLabel\":true}},\"columnOrder\":[\"f8721fc3-00f3-4106-96eb-0121ce00e6aa\",\"323e403c-e4a1-481d-897e-907192f1cc4a\",\"323e403c-e4a1-481d-897e-907192f1cc4aX1\",\"323e403c-e4a1-481d-897e-907192f1cc4aX0\"],\"sampling\":1,\"incompleteColumns\":{}}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"enhancements\":{},\"hidePanelTitles\":false},\"title\":\"Memory Usage\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":47,\"w\":22,\"h\":11,\"i\":\"542275ac-682e-43b8-b1ac-cf919d206e02\"},\"panelIndex\":\"542275ac-682e-43b8-b1ac-cf919d206e02\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"28f4234a-7411-4d15-8ad9-36ac978f7e19\",\"accessors\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241b\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\"}],\"yTitle\":\"System Load (0-100)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"28f4234a-7411-4d15-8ad9-36ac978f7e19\":{\"columns\":{\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\":{\"label\":\"Part of System Load\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.system.cpu.load_1m\",\"filter\":{\"query\":\"process.runtime.jvm.system.cpu.load_1m: *\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"04481efd-b3d8-44bc-be6c-5a0af56e241b\":{\"label\":\"System Load\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"last_value(process.runtime.jvm.system.cpu.load_1m, kql='process.runtime.jvm.system.cpu.load_1m: *')\",\"isFormulaBroken\":false},\"references\":[\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"customLabel\":true}},\"columnOrder\":[\"5e07b038-8927-4bdb-a933-ddbd1f7d0374\",\"04481efd-b3d8-44bc-be6c-5a0af56e241b\",\"04481efd-b3d8-44bc-be6c-5a0af56e241bX0\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"System Load\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":47,\"w\":26,\"h\":11,\"i\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\"},\"panelIndex\":\"a3aace75-debc-4370-9954-e5a6e8ac6259\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\",\"accessors\":[\"603e1430-a496-4249-b2da-73a5205b964e\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"splitAccessor\":\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"yConfig\":[{\"forAccessor\":\"603e1430-a496-4249-b2da-73a5205b964e\",\"axisMode\":\"left\"}],\"palette\":{\"type\":\"palette\",\"name\":\"status\"}}],\"yLeftScale\":\"log\",\"yTitle\":\"Megabytes\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"067c1db7-2942-4bf9-8fd6-4ca0a42a23fa\":{\"columns\":{\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"603e1430-a496-4249-b2da-73a5205b964eX0\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"last_value\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"process.runtime.jvm.memory.usage_after_last_gc\",\"filter\":{\"query\":\"process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap\",\"language\":\"kuery\"},\"params\":{\"sortField\":\"@timestamp\"},\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964eX1\":{\"label\":\"Part of divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"dataType\":\"number\",\"operationType\":\"math\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"tinymathAst\":{\"type\":\"function\",\"name\":\"divide\",\"args\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\",1048576],\"location\":{\"min\":0,\"max\":154},\"text\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\"}},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX0\"],\"customLabel\":true},\"603e1430-a496-4249-b2da-73a5205b964e\":{\"label\":\"MB\",\"dataType\":\"number\",\"operationType\":\"formula\",\"isBucketed\":false,\"scale\":\"ratio\",\"params\":{\"formula\":\"divide(last_value(process.runtime.jvm.memory.usage_after_last_gc, kql='process.runtime.jvm.memory.usage_after_last_gc: * and labels.type: heap'), 1048576)\",\"isFormulaBroken\":false},\"references\":[\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"filter\":{\"query\":\"\",\"language\":\"kuery\"},\"customLabel\":true},\"a9ec0126-a45f-4f86-b783-a4820badd7d8\":{\"label\":\"Top 10 values of labels.pool\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"labels.pool\",\"isBucketed\":true,\"params\":{\"size\":10,\"orderBy\":{\"type\":\"alphabetical\",\"fallback\":true},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false,\"parentFormat\":{\"id\":\"terms\"},\"include\":[],\"exclude\":[],\"includeIsRegex\":false,\"excludeIsRegex\":false}}},\"columnOrder\":[\"a9ec0126-a45f-4f86-b783-a4820badd7d8\",\"2ab4d7f4-49aa-4e45-9ad7-73256400df18\",\"603e1430-a496-4249-b2da-73a5205b964e\",\"603e1430-a496-4249-b2da-73a5205b964eX0\",\"603e1430-a496-4249-b2da-73a5205b964eX1\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Memory after GC\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":58,\"w\":22,\"h\":10,\"i\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\"},\"panelIndex\":\"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a3ee7019-98bb-45cd-8536-e36a8e58297e\",\"accessors\":[\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"9c350955-95a1-4726-93fd-717b104a218c\",\"yConfig\":[{\"forAccessor\":\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\",\"color\":\"#54b399\"}]}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a3ee7019-98bb-45cd-8536-e36a8e58297e\":{\"columns\":{\"9c350955-95a1-4726-93fd-717b104a218c\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":true,\"dropPartials\":false}},\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\":{\"label\":\"Threads count\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.threads.count\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"process.runtime.jvm.threads.count: *\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"9c350955-95a1-4726-93fd-717b104a218c\",\"ebf7c494-f676-4642-b2cf-ead2e8e93dee\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Threads\"},{\"version\":\"8.8.2\",\"type\":\"lens\",\"gridData\":{\"x\":22,\"y\":58,\"w\":26,\"h\":10,\"i\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\"},\"panelIndex\":\"4f987373-1b85-4e80-a7cc-c1c000349c5e\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"apm_static_index_pattern_id\",\"name\":\"indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"bottom\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yRightTitle\":\"Duration (ms)\",\"axisTitlesVisibilitySettings\":{\"x\":false,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"a45cd02a-4c62-4b34-bdce-c702b87b4246\",\"accessors\":[\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"position\":\"top\",\"seriesType\":\"bar\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"yConfig\":[{\"forAccessor\":\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"axisMode\":\"left\"},{\"forAccessor\":\"4e7a6993-fcee-47ae-9599-0047809bb29e\",\"color\":\"#e7664c\",\"axisMode\":\"left\"}]}],\"yTitle\":\"Duration (ms)\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"formBased\":{\"layers\":{\"a45cd02a-4c62-4b34-bdce-c702b87b4246\":{\"columns\":{\"57050bde-2c14-47d2-9dde-6e35326cf5cf\":{\"label\":\"@timestamp\",\"dataType\":\"date\",\"operationType\":\"date_histogram\",\"sourceField\":\"@timestamp\",\"isBucketed\":true,\"scale\":\"interval\",\"params\":{\"interval\":\"auto\",\"includeEmptyRows\":false,\"dropPartials\":false}},\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\":{\"label\":\"Minor GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action: \\\"end of minor GC\\\" \",\"language\":\"kuery\"},\"params\":{\"format\":{\"id\":\"number\",\"params\":{\"decimals\":2}},\"emptyAsNull\":true},\"customLabel\":true},\"4e7a6993-fcee-47ae-9599-0047809bb29e\":{\"label\":\"Major GC\",\"dataType\":\"number\",\"operationType\":\"median\",\"sourceField\":\"process.runtime.jvm.gc.duration\",\"isBucketed\":false,\"scale\":\"ratio\",\"filter\":{\"query\":\"labels.action : \\\"end of major GC\\\"\",\"language\":\"kuery\"},\"params\":{\"emptyAsNull\":true},\"customLabel\":true}},\"columnOrder\":[\"57050bde-2c14-47d2-9dde-6e35326cf5cf\",\"e3c6a7fb-d980-43d4-bb84-ae22018a0710\",\"4e7a6993-fcee-47ae-9599-0047809bb29e\"],\"incompleteColumns\":{},\"sampling\":1}}},\"textBased\":{\"layers\":{}}},\"internalReferences\":[],\"adHocDataViews\":{}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Garbage Collection\"}]","timeRestore":false,"title":"Layer7 Gateway Dashboard","version":1},"coreMigrationVersion":"8.8.0","created_at":"2024-05-14T07:28:39.093Z","id":"c29a9900-2161-11ee-be6c-57cbef857d54","managed":false,"references":[{"id":"apm_static_index_pattern_id","name":"675c235a-535c-48d9-a5ed-848810986e5a:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"0ae50fd8-f924-4207-b546-5d600ac701b3:indexpattern-datasource-layer-cad7aae7-308f-4c4f-abbd-b03792fbec56","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"ee3d33f8-243e-42d9-87fa-09c00dbfd1b7:indexpattern-datasource-layer-fd66544d-5ee7-4f36-8966-c7aa38197646","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"d9305573-c761-4802-9c0b-6f6326266125:indexpattern-datasource-layer-8638d8b0-f850-42f0-9e64-35e6f239e0d0","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-2381ace6-24bb-4e47-b57b-6fe06c980216","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"868932e9-487a-408f-89fd-14f9324534e9:indexpattern-datasource-layer-06696642-3076-4612-8291-189dcb25dd9e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"9a54b939-5371-49f6-8888-ab92e165e625:indexpattern-datasource-layer-a57f2de0-c4c8-4a18-9b3a-533ad5572097","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-04cd0fc2-f652-4583-a7d4-c1a8f6f8818f","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-b369a9a0-f9ee-4b7e-ba8f-97d2722cd89d","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"2a2057e2-ce45-4804-8ce0-3d4ed94679d7:indexpattern-datasource-layer-66ce3428-e822-4b54-afc8-69bbffd9c106","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"542275ac-682e-43b8-b1ac-cf919d206e02:indexpattern-datasource-layer-28f4234a-7411-4d15-8ad9-36ac978f7e19","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"a3aace75-debc-4370-9954-e5a6e8ac6259:indexpattern-datasource-layer-067c1db7-2942-4bf9-8fd6-4ca0a42a23fa","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"7f1f017e-d42b-4cf4-8cb2-3f4bb3de5367:indexpattern-datasource-layer-a3ee7019-98bb-45cd-8536-e36a8e58297e","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"4f987373-1b85-4e80-a7cc-c1c000349c5e:indexpattern-datasource-layer-a45cd02a-4c62-4b34-bdce-c702b87b4246","type":"index-pattern"},{"id":"apm_static_index_pattern_id","name":"controlGroup_d3e0d4d0-b1de-465c-8772-806a49213c2b:optionsListDataView","type":"index-pattern"}],"type":"dashboard","typeMigrationVersion":"8.7.0","updated_at":"2024-05-14T07:28:39.093Z","version":"WzE2ODksMV0="} {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file diff --git a/example/otel-elastic/instrumentation.yaml b/example/otel-elastic/instrumentation.yaml index 09550e28..f3add671 100644 --- a/example/otel-elastic/instrumentation.yaml +++ b/example/otel-elastic/instrumentation.yaml @@ -1,4 +1,4 @@ -apiVersion: opentelemetry.io/v1beta1 +apiVersion: opentelemetry.io/v1alpha1 kind: Instrumentation metadata: name: otel-instrumentation diff --git a/example/otel-lgtm/collector.yaml b/example/otel-lgtm/collector.yaml index f5bd99f6..e161fc20 100644 --- a/example/otel-lgtm/collector.yaml +++ b/example/otel-lgtm/collector.yaml @@ -1,11 +1,11 @@ -apiVersion: opentelemetry.io/v1alpha1 +apiVersion: opentelemetry.io/v1beta1 kind: OpenTelemetryCollector metadata: name: ssg-lgtm spec: mode: sidecar - image: otel/opentelemetry-collector-contrib:0.97.0 - config: | + image: otel/opentelemetry-collector-contrib:0.147.0 + config: receivers: otlp: protocols: @@ -15,6 +15,8 @@ spec: endpoint: 0.0.0.0:4318 processors: batch: + send_batch_max_size: 10000 + timeout: 10s transform: metric_statements: - context: datapoint @@ -25,11 +27,9 @@ spec: exporters: debug: verbosity: basic - loki: - endpoint: http://loki-loki-distributed-gateway.grafana-loki.svc.cluster.local/loki/api/v1/push prometheusremotewrite: - endpoint: http://mimir-nginx.grafana-loki.svc.cluster.local/api/v1/push - otlp: + endpoint: http://mimir-gateway.grafana-loki.svc.cluster.local/api/v1/push + otlp/2: endpoint: tempo.grafana-loki.svc.cluster.local:4317 tls: insecure: true @@ -37,20 +37,18 @@ spec: telemetry: logs: level: "debug" - metrics: - address: "0.0.0.0:8888" pipelines: traces: receivers: [otlp] processors: [batch] - exporters: [otlp] + exporters: [otlp/2] metrics: receivers: [otlp] processors: [transform,batch] exporters: [prometheusremotewrite, debug] logs: receivers: [otlp] - exporters: [debug,loki] + exporters: [debug] extensions: health_check: pprof: diff --git a/example/otel-lgtm/layer7-operator/collector.yaml b/example/otel-lgtm/layer7-operator/collector.yaml index 66edd098..cea6bded 100644 --- a/example/otel-lgtm/layer7-operator/collector.yaml +++ b/example/otel-lgtm/layer7-operator/collector.yaml @@ -1,11 +1,11 @@ -apiVersion: opentelemetry.io/v1alpha1 +apiVersion: opentelemetry.io/v1beta kind: OpenTelemetryCollector metadata: name: layer7-operator spec: mode: deployment image: otel/opentelemetry-collector-contrib:0.97.0 - config: | + config: receivers: otlp: protocols: @@ -19,7 +19,7 @@ spec: debug: verbosity: basic prometheusremotewrite: - endpoint: http://mimir-nginx.grafana-loki.svc.cluster.local/api/v1/push + endpoint: http://mimir-gateway.grafana-loki.svc.cluster.local/api/v1/push service: telemetry: logs: diff --git a/example/otel-lgtm/prometheus/grafana-dashboard/layer7-gateway-dashboard.json b/example/otel-lgtm/prometheus/grafana-dashboard/layer7-gateway-dashboard.json index 946adb20..65c2b9d2 100644 --- a/example/otel-lgtm/prometheus/grafana-dashboard/layer7-gateway-dashboard.json +++ b/example/otel-lgtm/prometheus/grafana-dashboard/layer7-gateway-dashboard.json @@ -91,7 +91,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(l7_service_attempted_count_total{job=\"$gatewayDeployment\"}) ", + "expr": "sum(layer7_service_attempted_count_total{job=\"$gatewayDeployment\"}) ", "instant": false, "interval": "", "legendFormat": "{{podName}}", @@ -158,7 +158,7 @@ }, "editorMode": "code", "exemplar": true, - "expr": "sum(l7_service_success_count_total{job=\"$gatewayDeployment\"})", + "expr": "sum(layer7_service_success_count_total{job=\"$gatewayDeployment\"})", "interval": "", "legendFormat": "{{pod}}", "range": true, @@ -226,7 +226,7 @@ }, "editorMode": "code", "exemplar": true, - "expr": "sum(l7_service_attempted_count_total{job=\"$gatewayDeployment\"} - l7_service_success_count_total{job=\"$gatewayDeployment\"})", + "expr": "sum(layer7_service_attempted_count_total{job=\"$gatewayDeployment\"} - layer7_service_success_count_total{job=\"$gatewayDeployment\"})", "interval": "", "legendFormat": "{{pod}}", "range": true, @@ -323,7 +323,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(rate(l7_service_attempted_count_total{job=\"$gatewayDeployment\"}[5m]))", + "expr": "sum(rate(layer7_service_attempted_count_total{job=\"$gatewayDeployment\"}[5m]))", "instant": false, "interval": "", "legendFormat": "${gatewayDeployment}", @@ -418,7 +418,7 @@ }, "editorMode": "code", "exemplar": true, - "expr": "l7_service_policy_violations_count_total{job=\"$gatewayDeployment\"}", + "expr": "layer7_service_policy_violations_count_total{job=\"$gatewayDeployment\"}", "format": "table", "instant": true, "interval": "", @@ -441,8 +441,8 @@ "goid": true, "instance": true, "job": true, - "l7_goid": true, - "l7_method": false, + "layer7_goid": true, + "layer7_method": false, "name": true, "namespace": true, "pod": false, @@ -456,9 +456,9 @@ "__name__": "", "apiName": "Service Name", "container": "", - "l7_method": "Method", - "l7_serviceName": "Service Name", - "l7_serviceUri": "Service URI", + "layer7_method": "Method", + "layer7_serviceName": "Service Name", + "layer7_serviceUri": "Service URI", "namespace": "", "pod": "Pod", "service": "" @@ -559,10 +559,10 @@ }, "editorMode": "code", "exemplar": false, - "expr": "rate(l7_service_attempted_count_total{l7_serviceName=\"$serviceName\",job=\"$gatewayDeployment\",l7_method=\"$requestMethod\"}[5m])", + "expr": "rate(layer7_service_attempted_count_total{layer7_serviceName=\"$serviceName\",job=\"$gatewayDeployment\",layer7_method=\"$requestMethod\"}[5m])", "instant": false, "interval": "", - "legendFormat": "{{l7_serviceName}}", + "legendFormat": "{{layer7_serviceName}}", "refId": "A" } ], @@ -627,9 +627,9 @@ }, "editorMode": "code", "exemplar": true, - "expr": "l7_service_latency_milliseconds_sum{job=\"$gatewayDeployment\"} / l7_service_latency_milliseconds_count{job=\"$gatewayDeployment\"}", + "expr": "layer7_service_latency_milliseconds_sum{job=\"$gatewayDeployment\"} / layer7_service_latency_milliseconds_count{job=\"$gatewayDeployment\"}", "interval": "", - "legendFormat": "{{pod}}-{{l7_serviceName}}", + "legendFormat": "{{pod}}-{{layer7_serviceName}}", "range": true, "refId": "A" } @@ -712,10 +712,10 @@ }, "editorMode": "code", "exemplar": false, - "expr": "histogram_quantile(0.95, l7_service_latency_milliseconds_bucket{})", + "expr": "histogram_quantile(0.95, layer7_service_latency_milliseconds_bucket{})", "format": "time_series", "instant": false, - "legendFormat": "{{l7_serviceName}}", + "legendFormat": "{{layer7_serviceName}}", "range": true, "refId": "A" } @@ -837,7 +837,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "l7_service_attempted_count_total{job=\"$gatewayDeployment\", l7_method=\"$requestMethod\"}", + "expr": "layer7_service_attempted_count_total{job=\"$gatewayDeployment\", layer7_method=\"$requestMethod\"}", "format": "table", "instant": true, "interval": "", @@ -852,9 +852,9 @@ "options": { "include": { "names": [ - "l7_method", - "l7_serviceName", - "l7_serviceUri", + "layer7_method", + "layer7_serviceName", + "layer7_serviceUri", "Value", "pod" ] @@ -870,9 +870,9 @@ "renameByName": { "Value": "Count", "exported_job": "", - "l7_method": "Method", - "l7_serviceName": "Service Name", - "l7_serviceUri": "Service URI", + "layer7_method": "Method", + "layer7_serviceName": "Service Name", + "layer7_serviceUri": "Service URI", "pod": "Pod" } } @@ -961,7 +961,7 @@ } ], "limit": 20, - "query": "{.k8s.deployment.name=\"$gatewayDeployment\" && .k8s.pod.name=\"$gatewayPod\" && .l7_serviceName=\"$serviceName\"}", + "query": "{.k8s.pod.name=\"$gatewayPod\" && .layer7_serviceName=\"$serviceName\"}", "queryType": "traceql", "refId": "A", "spss": 3, @@ -1431,22 +1431,19 @@ "list": [ { "allValue": "\"\"", - "current": { - "text": "ssg-otel-gateway", - "value": "ssg-otel-gateway" - }, + "current": {}, "datasource": { "type": "prometheus", "uid": "mimir" }, - "definition": "l7_service_attempted_count_total{job!=\"\"}", + "definition": "layer7_service_attempted_count_total{job!=\"\"}", "includeAll": false, "label": "Gateway Deployment", "name": "gatewayDeployment", "options": [], "query": { "qryType": 4, - "query": "l7_service_attempted_count_total{job!=\"\"}", + "query": "layer7_service_attempted_count_total{job!=\"\"}", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, @@ -1455,23 +1452,19 @@ }, { "allValue": "\"\"", - "current": { - "isNone": true, - "text": "None", - "value": "" - }, + "current": {}, "datasource": { "type": "prometheus", "uid": "mimir" }, - "definition": "l7_service_attempted_count_total{pod!=\"\",job=\"$gatewayDeployment\"}", + "definition": "layer7_service_attempted_count_total{pod!=\"\",job=\"$gatewayDeployment\"}", "includeAll": false, "label": "Pod", "name": "gatewayPod", "options": [], "query": { "qryType": 4, - "query": "l7_service_attempted_count_total{pod!=\"\",job=\"$gatewayDeployment\"}", + "query": "layer7_service_attempted_count_total{pod!=\"\",job=\"$gatewayDeployment\"}", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, @@ -1480,50 +1473,44 @@ }, { "allValue": "\"\"", - "current": { - "text": "Test12345", - "value": "Test12345" - }, + "current": {}, "datasource": { "type": "prometheus", "uid": "mimir" }, - "definition": "l7_service_attempted_count_total{l7_serviceName!=\"\", job=\"$gatewayDeployment\"}", + "definition": "layer7_service_attempted_count_total{layer7_serviceName!=\"\", job=\"$gatewayDeployment\"}", "includeAll": false, "label": "Service Name", "name": "serviceName", "options": [], "query": { "qryType": 4, - "query": "l7_service_attempted_count_total{l7_serviceName!=\"\", job=\"$gatewayDeployment\"}", + "query": "layer7_service_attempted_count_total{layer7_serviceName!=\"\", job=\"$gatewayDeployment\"}", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, - "regex": "/.*l7_serviceName=\"([^\"]*)\"/", + "regex": "/.*layer7_serviceName=\"([^\"]*)\"/", "type": "query" }, { "allValue": "\"\"", - "current": { - "text": "GET", - "value": "GET" - }, + "current": {}, "datasource": { "type": "prometheus", "uid": "mimir" }, - "definition": "l7_service_attempted_count_total{l7_method!=\"\",l7_serviceName=\"$serviceName\", job=\"$gatewayDeployment\"}", + "definition": "layer7_service_attempted_count_total{layer7_method!=\"\",layer7_serviceName=\"$serviceName\", job=\"$gatewayDeployment\"}", "includeAll": false, "label": "Request Method", "name": "requestMethod", "options": [], "query": { "qryType": 4, - "query": "l7_service_attempted_count_total{l7_method!=\"\",l7_serviceName=\"$serviceName\", job=\"$gatewayDeployment\"}", + "query": "layer7_service_attempted_count_total{layer7_method!=\"\",layer7_serviceName=\"$serviceName\", job=\"$gatewayDeployment\"}", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, - "regex": "/.*l7_method=\"([^\"]*)\"/", + "regex": "/.*layer7_method=\"([^\"]*)\"/", "type": "query" } ] diff --git a/example/otel-lgtm/prometheus/prometheus-values.yaml b/example/otel-lgtm/prometheus/prometheus-values.yaml index 62f9aa39..008d1c60 100644 --- a/example/otel-lgtm/prometheus/prometheus-values.yaml +++ b/example/otel-lgtm/prometheus/prometheus-values.yaml @@ -78,7 +78,7 @@ grafana: editable: true orgId: 1 type: prometheus - url: http://mimir-nginx.grafana-loki.svc.cluster.local/prometheus + url: http://mimir-gateway.grafana-loki.svc.cluster.local/prometheus - name: tempo-minimal id: 6 editable: true @@ -106,13 +106,11 @@ grafana: ## IngressClassName for Grafana Ingress. ## Should be provided if Ingress is enable. ## - ingressClassName: nginx + ingressClassName: contour ## Annotations for Grafana Ingress ## annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" ## Labels to be added to the Ingress ## diff --git a/example/otel-lgtm/readme.md b/example/otel-lgtm/readme.md index f4ccd07a..312a6886 100644 --- a/example/otel-lgtm/readme.md +++ b/example/otel-lgtm/readme.md @@ -21,7 +21,7 @@ As part of this guide we will deploy the following components ## Prerequisites - Kubernetes v1.26+ - Ingress Controller (important for grafana) - - the quickstart examples deploy the nginx ingress controller + - the quickstart examples deploy the contour ingress controller You will also need to add the following entries to your hosts file @@ -42,8 +42,8 @@ kubectl get ingress -n monitoring ``` output ``` -NAME CLASS HOSTS ADDRESS PORTS AGE -prometheus-grafana nginx grafana.brcmlabs.com 80, 443 57m +NAME CLASS HOSTS ADDRESS PORTS AGE +prometheus-grafana contour grafana.brcmlabs.com 80, 443 57m ``` /etc/hosts - the ingress address will be the same for the Gateway Ingress record ``` @@ -62,15 +62,15 @@ prometheus-grafana nginx grafana.brcmlabs.com 80, 443 57m ``` 3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - these will be referenced later on. -4. You will need an ingress controller like nginx +4. You will need an ingress controller like contour - if you do not have one installed already you can use the makefile in the example directory to deploy one - ```cd example``` - Generic Kubernetes - - ```make nginx``` + - ```make contour``` - Kind (Kubernetes in Docker) - follow the steps in Quickstart or - - ```make nginx-kind``` + - ```make contour-kind``` - return to the previous folder - ```cd ..``` @@ -79,7 +79,7 @@ This example uses multiple namespaces for the additional components. Your Kubern The following namespaces will be created/used if you use the [quickstart](#quickstart) option to deploy this example - monitoring (prometheus, grafana) - grafana-loki (loki, tempo, promtail) -- ingress-nginx (nginx ingress controller) +- projectcontour (contour ingress controller) If you deploy the OpenTelemetry Operator and Certmanager - cert-manager @@ -92,7 +92,7 @@ If you wish to use a different host and or configure a signed certificate for Gr grafana: ingress: enabled: true - ingressClassName: nginx + ingressClassName: contour labels: {} hosts: - grafana.brcmlabs.com @@ -113,10 +113,8 @@ If you wish to use a different host and or configure a signed certificate for th ``` ingress: enabled: true - ingressClassName: nginx - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - # nginx.ingress.kubernetes.io/ssl-passthrough: "true" + ingressClassName: contour + annotations: {} tls: - hosts: - gateway.brcmlabs.com @@ -174,13 +172,13 @@ make kind-cluster otel-lgtm-example-kind enable-otel-l7operator ``` make otel-lgtm-example enable-otel-l7operator ``` -if you don't have an ingress controller you can deploy nginx with the following +if you don't have an ingress controller you can deploy contour with the following ``` -make nginx +make contour ``` if you are using kind ``` -make nginx-kind +make contour-kind ``` #### Enabling OTel Metrics for the Layer7 Operator @@ -499,30 +497,31 @@ kubectl get pods -n grafana-loki ``` output ``` -NAME READY STATUS RESTARTS AGE -loki-0 1/1 Running 0 4m -loki-canary-hr5n6 1/1 Running 0 4m -loki-chunks-cache-0 2/2 Running 0 4m -loki-gateway-5cd888fb5b-lx68d 1/1 Running 0 4m -loki-minio-0 1/1 Running 0 4m -loki-results-cache-0 2/2 Running 0 4m -mimir-alertmanager-0 1/1 Running 0 2m7s -mimir-compactor-0 1/1 Running 0 2m7s -mimir-distributor-75db4845b5-fwgx5 1/1 Running 0 2m7s -mimir-ingester-0 1/1 Running 0 2m7s -mimir-ingester-1 1/1 Running 0 2m7s -mimir-make-minio-buckets-5.0.14-9ddtx 0/1 Completed 0 2m7s -mimir-minio-66c9c9446c-vr6tx 1/1 Running 0 2m7s -mimir-nginx-6c54df9bbf-xw5wm 1/1 Running 0 2m7s -mimir-overrides-exporter-75c74b879-l4vd2 1/1 Running 0 2m7s -mimir-querier-f4c7668c7-c2lkr 1/1 Running 0 2m7s -mimir-query-frontend-86f49bdd54-57njx 1/1 Running 0 2m7s -mimir-query-scheduler-9c9db55-ns8bw 1/1 Running 0 2m7s -mimir-rollout-operator-589445cccd-2gktd 1/1 Running 0 2m7s -mimir-ruler-87b575f97-xxv8z 1/1 Running 0 2m7s -mimir-store-gateway-0 1/1 Running 0 2m7s -promtail-92scb 1/1 Running 0 3m25s -tempo-0 1/1 Running 0 3m17s +NAME READY STATUS RESTARTS AGE +loki-0 2/2 Running 0 3m +loki-canary-dnc8b 1/1 Running 0 3m +loki-chunks-cache-0 2/2 Running 0 3m +loki-gateway-599bd8b86-wdpqg 1/1 Running 0 3m +loki-minio-0 1/1 Running 0 3m +loki-results-cache-0 2/2 Running 0 3m +mimir-alertmanager-0 1/1 Running 0 3m +mimir-compactor-0 1/1 Running 0 3m +mimir-distributor-7495b688fb-s4jgc 1/1 Running 0 3m +mimir-gateway-647dcf69cb-bbzzl 1/1 Running 0 3m +mimir-ingester-0 1/1 Running 0 3m +mimir-ingester-1 1/1 Running 0 3m +mimir-kafka-0 1/1 Running 0 3m +mimir-make-minio-buckets-5.4.0-xvxc6 0/1 Completed 0 3m +mimir-minio-76f9f549db-4pvpm 1/1 Running 0 3m +mimir-overrides-exporter-7d7b775fb8-w4pm9 1/1 Running 0 3m +mimir-querier-85c594d56c-46d5m 1/1 Running 0 3m +mimir-query-frontend-557b948666-sfrsf 1/1 Running 0 3m +mimir-query-scheduler-9857bc85f-lkb5l 1/1 Running 0 3m +mimir-rollout-operator-65b97658df-8dd98 1/1 Running 0 3m +mimir-ruler-65f68d7c8d-zrhwj 1/1 Running 0 3m +mimir-store-gateway-0 1/1 Running 0 3m +promtail-2kvn4 1/1 Running 0 3m +tempo-0 1/1 Running 0 3m ``` - Confirm all datasources are configured correctly in Grafana @@ -542,8 +541,8 @@ tempo-0 1/1 Running 0 3m17s ``` kubectl get ingress -NAME CLASS HOSTS ADDRESS PORTS AGE -ssg nginx gateway.brcmlabs.com 34.89.126.80 80, 443 54m +NAME CLASS HOSTS ADDRESS PORTS AGE +ssg contour gateway.brcmlabs.com 34.89.126.80 80, 443 54m ``` Add the following to your hosts file for DNS resolution @@ -601,13 +600,7 @@ If you used an existing Kubernetes Cluster make uninstall ``` -- If you deployed nginx - -If you used kind -``` -make uninstall-nginx-kind -``` -If you used an existing Kubernetes Cluster +- If you deployed contour ``` -make uninstall-nginx +make uninstall-contour ``` diff --git a/example/otel-prometheus/README.md b/example/otel-prometheus/README.md deleted file mode 100644 index ed179e56..00000000 --- a/example/otel-prometheus/README.md +++ /dev/null @@ -1,849 +0,0 @@ -# Open Telemetry Integration (Prometheus) -By the end of this example you should have a better understanding of the how to utilise the Gateway's Open Telemetry integration to increase observability across your Operator Managed Gateways. This example uses a Simple Gateway as a base and includes installing the following. - -- CertManager Operator (required for OTel) -- OpenTelemetry Operator -- Prometheus Stack -- Nginx (Ingress Controller) -- Jaeger - -![otel-example-overview](../images/otel-example-components-overview.png) - -## Prerequisites -- Kubernetes v1.25+ -- Gateway v11.x License -- Ingress Controller (important for grafana and jaeger) - -### Getting started -1. Place a gateway v11 license in [base/resources/secrets/license/](../base/resources/secrets/license/) called license.xml. -2. Accept the Gateway License - - license.accept defaults to false in [Gateway examples](../gateway/otel-prometheus-gateway.yaml) - - update license.accept to true before proceeding - ``` - license: - accept: true - secretName: gateway-license - ``` -3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - - these will be referenced later on. -4. You will need an ingress controller like nginx - - if you do not have one installed already you can use the makefile in the example directory to deploy one - - ```cd example``` - - Generic Kubernetes - - ```make nginx``` - - Kind (Kubernetes in Docker) - - follow the steps in Quickstart - or - - ```make nginx-kind``` - - return to the previous folder - - ```cd ..``` - -The OTel Example requires multiple namespaces for the additional components. Your Kubernetes user or service account must have sufficient privileges to create namespaces, deployments, configmaps, secrets, service accounts, roles, etc. - -***NOTE:*** to keep things simple we use the default namespace when creating namespaced resources including the Layer7 Operator, Gateway, Repositories and Prometheus Service Monitor. If you'd like to use a different namespace then you will need to update the following. - -- [servicemonitor.yaml](./servicemonitor.yaml) -``` -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - app: ssg - name: ssg -spec: - endpoints: - - interval: 10s - path: /metrics - port: monitoring - jobLabel: ssg - namespaceSelector: - matchNames: - - default ==> change this to your namespace - selector: - matchLabels: - app.kubernetes.io/created-by: layer7-operator - app.kubernetes.io/managed-by: layer7-operator - app.kubernetes.io/name: ssg - app.kubernetes.io/part-of: ssg -``` - -- [prometheus-values.yaml](./monitoring/prometheus/prometheus-values.yaml) -``` -prometheusOperator: - namespaces: - releaseNamespace: true - additional: - - kube-system - - layer7 - - default - - addyournamespacehere -... -``` - -- Update your Kubectl context -``` -kubectl config set-context --current --namespace=yournamespace -``` - -If you have a docker machine available you can use [Kind](https://kind.sigs.k8s.io/) to try out this example! - -### Getting started -1. Place a gateway v11 license in [base/resources/secrets/license/](../base/resources/secrets/license/) called license.xml. -2. Accept the Gateway License - - license.accept defaults to false in [Gateway examples](../gateway/otel-prometheus-gateway.yaml) - - update license.accept to true before proceeding -3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - - these will be referenced later on. -4. You will need an ingress controller like nginx - - if you do not have one installed already you can use the makefile in the example directory to deploy one - - ```cd example``` - - Generic Kubernetes - - ```make nginx``` - - Kind (Kubernetes in Docker) - - follow the steps in Quickstart - or - - ```make nginx-kind``` - - return to the previous folder - - ```cd ..``` - -### Gateway Configuration -- [Container Gateway](#container-gateway-configuration) -- [OTel Collector](#otel-collector-configuration) -- [Service Monitor](#service-monitor-configuration) -- [Gateway Application](#gateway-application-configuration) -- [Service Level](#service-level-configuration) - -### Guide -- [Quickstart Kind](#quickstart-kind) -- [Quickstart Existing Kubernetes Cluster](#quickstart-existing-cluster) - -### Monitoring/Observability Components -- [Install Cert Manager](#install-cert-manager) -- [Install Open Telemetry](#install-open-telemetry) -- [Install Prometheus](#install-prometheus) -- [Install Jaeger](#install-jaeger) -- [Install an Ingress Controller(optional)](#install-nginx) - -### Layer7 Operator -- [Deploy the Operator](#deploy-the-layer7-operator) -- [Create Repositories](#create-repository-custom-resources) -- [Create a Gateway](#create-a-gateway) -- [Test Gateway Deployment](#test-your-gateway-deployment) -- [Remove Kind Cluster](#remove-kind-cluster) -- [Uninstall Demo Components and Custom Resources](#remove-demo-component-and-custom-resources) -- [Uninstall the Operator CRs](#uninstall-the-operator) - -### Container Gateway Configuration -The container gateway configuration required for this integration is relatively simple. We will set some environment variables in our OTel [instrumentation](./instrumentation.yaml) that the Otel Agent present on the Container Gateway will use to send logs, traces and metrics to the Otel Collector sidecar. - -``` -apiVersion: opentelemetry.io/v1alpha1 -kind: Instrumentation -metadata: - name: otel-instrumentation -spec: - exporter: - endpoint: http://localhost:4317 - propagators: - - tracecontext - - baggage - - b3 - sampler: - type: parentbased_traceidratio - argument: "0.25" -``` - -We also need to expose a monitoring endpoint so that Prometheus can scrape the Gateway service -``` -service: - type: LoadBalancer - ports: - ... - - name: monitoring - port: 8889 - protocol: TCP -``` - -### OTel Collector Configuration -The OpenTelemetry Operator will automatically inject a sidecar into the Gateway Pod with our [OTel configuration](./collector.yaml). This configuration exposes a /metrics endpoint on the Gateway Pod on port 8889 for Prometheus to scrape. - -``` -receivers: - otlp: - protocols: - grpc: - http: -processors: - batch: -exporters: - logging: - loglevel: warn - prometheus: - endpoint: "0.0.0.0:8889" - const_labels: - name: ssg - jaeger: - endpoint: simple-allinone-collector:14250 - tls: - insecure: true -service: - telemetry: - logs: - level: "debug" - metrics: - address: "0.0.0.0:8888" - pipelines: - traces: - receivers: [otlp] - processors: [batch] - exporters: [jaeger] - metrics: - receivers: [otlp] - processors: [batch] - exporters: [prometheus, logging] - logs: - receivers: [otlp] - exporters: [logging] -extensions: - health_check: - pprof: - endpoint: 0.0.0.0:1777 - zpages: - endpoint: 0.0.0.0:55679 -``` - -### Service Monitor Configuration -We need to tell Prometheus about the /metrics endpoint on the Gateway pod, we do this with a [ServiceMonitor](./servicemonitor.yaml) - -``` -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - app: ssg - name: ssg -spec: - endpoints: - - interval: 10s ==> time between scrapes - path: /metrics ==> the time - port: monitoring - jobLabel: ssg - namespaceSelector: - matchNames: - - default ==> if you are not using the default namespace you will need to update this - selector: - matchLabels: - app.kubernetes.io/created-by: layer7-operator - app.kubernetes.io/managed-by: layer7-operator - app.kubernetes.io/name: ssg - app.kubernetes.io/part-of: ssg -``` - -### Gateway Application Configuration -The Gateway has OTel specific configuration. - -- Pod Annotations -``` - app: - podAnnotations: - sidecar.opentelemetry.io/inject: "ssg-eck" <== this injects the OpenTelemetryCollector sidecar - instrumentation.opentelemetry.io/inject-java: "true" <== this loads the OpenTelemetry Java agent onto the Gateway - instrumentation.opentelemetry.io/container-names: "gateway" <== this defines which container the Java Agent is loaded onto -``` - -- Cluster-Wide Properties -``` -cwp: - enabled: true - properties: - ... - - name: otel.enabled - value: "true" - - name: otel.serviceMetricEnabled - value: "true" - - name: otel.traceEnabled - value: "true" - - name: otel.metricPrefix - value: l7_ - - name: otel.traceConfig - value: | - { - "services": [ - {"resolutionPath": ".*"} - ] - } -``` - -- System Properties -``` -system: - properties: |- - ... - # OpenTelemetry Agent Configuration - otel.instrumentation.common.default-enabled=true - otel.instrumentation.opentelemetry-api.enabled=true - otel.instrumentation.runtime-metrics.enabled=true - otel.instrumentation.runtime-telemetry.enabled=true - otel.instrumentation.opentelemetry-instrumentation-annotations.enabled=true - otel.java.global-autoconfigure.enabled=true - # Additional properties go here -``` - -### Quickstart Kind -A Makefile is included in the example directory that makes deploying this example a one step process. If you have access to a Docker Machine you can use [Kind](https://kind.sigs.k8s.io/) (Kubernetes in Docker). This example can optionally deploy a Kind Cluster for you (you just need to make sure that you've [installed Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)) - -The kind configuration is in the base of the example folder. If your docker machine is remote (you are using a VM or remote machine) then uncomment the network section and set the apiServerAddress to the address of your VM/Remote machine -``` -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -# networking: -# apiServerAddress: "192.168.1.64" -# apiServerPort: 6443 -nodes: -- role: control-plane - kubeadmConfigPatches: - - | - kind: InitConfiguration - nodeRegistration: - kubeletExtraArgs: - node-labels: "ingress-ready=true" - extraPortMappings: - - containerPort: 80 - hostPort: 80 - protocol: TCP - - containerPort: 443 - hostPort: 443 - protocol: TCP -``` - -This process takes a few minutes to complete - -- navigate to the example directory -``` -cd example -``` -- deploy the example -``` -make kind-cluster otel-prometheus-example-kind -``` -You will also need to add the following entries to your hosts file -``` -127.0.0.1 gateway.brcmlabs.com jaeger.brcmlabs.com grafana.brcmlabs.com -``` - -- wait for all components to be ready -``` -watch kubectl get pods -``` -output -``` -NAME READY STATUS RESTARTS AGE -layer7-operator-controller-manager-69dc945d66-prshk 2/2 Running 0 6m49s -simple-allinone-7d76ff5b7c-5nqnf 1/1 Running 0 4m40s -ssg-84cd5d6c86-r7vm2 2/2 Running 0 3m13s -``` - -You can now move on to test your gateway deployment! -- [Test Gateway Deployment](#test-your-gateway-deployment) - -### Quickstart Existing Cluster -This quickstart uses the Makefile to install of the necessary components into your Kubernetes Cluster. - -- navigate to the example directory -``` -cd example -``` -- deploy the example -``` -make otel-prometheus-example -``` - -If you don't already have an ingress controller you can deploy nginx with the following command - -- Suppports most Kubernetes Environments -``` -make nginx -``` -- If you're using Kind -``` -make nginx-kind -``` -- Additional options can be found here -``` -https://github.com/kubernetes/ingress-nginx/tree/main/deploy/static/provider -``` - -- Once nginx has been installed get the L4 LoadBalancer address -``` -kubectl get svc -n ingress-nginx -``` -output -``` -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -ingress-nginx-controller LoadBalancer 10.152.183.52 192.168.1.190 80:30183/TCP,443:30886/TCP 24m -ingress-nginx-controller-admission ClusterIP 10.152.183.132 443/TCP 24m -``` -- You probably don't have access to a DNS server for this demo so you will need to add the following entries to your hosts file -``` -EXTERNAL-IP gateway.brcmlabs.com jaeger.brcmlabs.com grafana.brcmlabs.com - -example -192.168.1.190 gateway.brcmlabs.com jaeger.brcmlabs.com grafana.brcmlabs.com -``` - -- wait for all components to be ready -``` -watch kubectl get pods -``` -output -``` -NAME READY STATUS RESTARTS AGE -layer7-operator-controller-manager-69dc945d66-prshk 2/2 Running 0 6m49s -simple-allinone-7d76ff5b7c-5nqnf 1/1 Running 0 4m40s -ssg-84cd5d6c86-r7vm2 2/2 Running 0 3m13s -``` - -You can now move on to test your gateway deployment! -- [Test Gateway Deployment](#test-your-gateway-deployment) - -### Install Cert Manager -These steps are based the official documentation for installing Cert-Manager [here](https://cert-manager.io/docs/installation/). Cert-Manager is a pre-requisite for the Open Telemetry Operator. -``` - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml -``` - -#### View CertManager Components -***Cert manager needs to be running before you continue onto the next step.*** -``` -kubectl get all -n cert-manager - -NAME READY STATUS RESTARTS AGE -pod/cert-manager-cainjector-5fcd49c96-r97pk 1/1 Running 0 34s -pod/cert-manager-6ffb79dfdb-thpft 1/1 Running 0 34s -pod/cert-manager-webhook-796ff7697b-5gbzw 1/1 Running 0 34s - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/cert-manager ClusterIP 10.152.183.216 9402/TCP 34s -service/cert-manager-webhook ClusterIP 10.152.183.85 443/TCP 34s - -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/cert-manager-cainjector 1/1 1 1 34s -deployment.apps/cert-manager 1/1 1 1 34s -deployment.apps/cert-manager-webhook 1/1 1 1 34s - -NAME DESIRED CURRENT READY AGE -replicaset.apps/cert-manager-cainjector-5fcd49c96 1 1 1 34s -replicaset.apps/cert-manager-6ffb79dfdb 1 1 1 34s -replicaset.apps/cert-manager-webhook-796ff7697b 1 1 1 34s -``` - - -### Install Open Telemetry -These steps are based the official documentation for installing Open Telemetry [here](https://cert-manager.io/docs/installation/). Open Telemetry depends on cert-manager, ***make sure that cert-manager is running before installing open telemetry.*** - -- Install the Open Telemetry Operator. -``` -kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml -``` - -#### View Open Telemetry Components -``` -kubectl get all -n opentelemetry-operator-system - -NAME READY STATUS RESTARTS AGE -pod/opentelemetry-operator-controller-manager-5d84764d4b-6zdtb 2/2 Running 18 (8d ago) 28d - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/opentelemetry-operator-controller-manager-metrics-service ClusterIP 10.68.1.93 8443/TCP 72d -service/opentelemetry-operator-webhook-service ClusterIP 10.68.3.243 443/TCP 72d - -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/opentelemetry-operator-controller-manager 1/1 1 1 72d - -NAME DESIRED CURRENT READY AGE -replicaset.apps/opentelemetry-operator-controller-manager-5d84764d4b 1 1 1 72d -``` - -***The OpenTelemetry Operator needs to be running before you continue onto the next step.*** -- Create an OpenTelemetryCollector resource -``` -kubectl apply -f ./example/otel-prometheus/collector.yaml -``` -- Create OpenTelemetry Instrumentation -``` -kubectl apply -f ./example/otel-prometheus/instrumentation.yaml -``` - -### Install the Prometheus Stack -These steps are based on instructions that can be found in the Prometheus Community Helm Chart [documentation](https://github.com/prometheus-community/helm-charts/tree/main/charts/prometheus). - -- Add the Prometheus Community Helm Chart repository -``` -helm repo add prometheus-community https://prometheus-community.github.io/helm-charts -helm repo update -``` -- Create a namespace called monitoring -``` -kubectl create ns monitoring -``` -- Create the Layer7 Grafana Dashboard -``` -kubectl apply -k ./example/otel-prometheus/monitoring/grafana/ -``` -- Install the Prometheus Helm Chart in the monitoring namespace -``` -helm upgrade -i prometheus -f ./example/otel-prometheus/monitoring/prometheus/prometheus-values.yaml prometheus-community/kube-prometheus-stack -n monitoring -``` -- Create a Service Monitor -``` -kubectl apply -f ./example/otel-prometheus/servicemonitor.yaml -``` - -### Install Jaeger -These steps are based on instructions that can be found in the Jaeger [documentation](https://www.jaegertracing.io/docs/1.65/operator/) - -- Create a namespace called observability -``` -kubectl create namespace observability -``` -- Install the Jaeger Operator -``` -kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability -``` - -#### View Jaeger Components -``` -kubectl get all -n observability - -NAME READY STATUS RESTARTS AGE -pod/jaeger-operator-6cf68b6f65-z8jtv 2/2 Running 0 16s - -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -service/jaeger-operator-metrics ClusterIP 10.68.15.46 8443/TCP 17s -service/jaeger-operator-webhook-service ClusterIP 10.68.1.180 443/TCP 17s - -NAME READY UP-TO-DATE AVAILABLE AGE -deployment.apps/jaeger-operator 1/1 1 1 17s - -NAME DESIRED CURRENT READY AGE -replicaset.apps/jaeger-operator-6cf68b6f65 1 1 1 17s -``` - -- Deploy a Jaeger Custom Resource -``` -kubectl apply -f ./example/otel-prometheus/observability/jaeger/jaeger.yaml -``` - -- Create an ingress resource for Jaeger. -``` -kubectl apply -f ./example/otel-prometheus/observability/jaeger/ingress.yaml -``` - -### Install Nginx -This command will deploy an nginx ingress controller. If you already have nginx or another ingress controller running in your Kubernetes cluster you can safely ignore this step. - -Suppports most Kubernetes Environments -``` -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml -``` -If you're using Kind -``` -kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml -``` -Additional options can be found here -``` -https://github.com/kubernetes/ingress-nginx/tree/main/deploy/static/provider -``` - -### Deploy the Layer7 Operator -This step will deploy the Layer7 Operator and all of its resources in namespaced mode. This means that it will only manage Gateway and Repository Custom Resources in the Kubernetes Namespace that it's deployed in. - -``` -kubectl apply -f https://github.com/CAAPIM/layer7-operator/releases/download/v1.2.2/bundle.yaml -``` - -#### Verify the Operator is up and running -``` -kubectl get pods - -NAME READY STATUS RESTARTS AGE -layer7-operator-controller-manager-7647b58697-qd9vg 2/2 Running 0 27s -``` - -### Create Repository Custom Resources -This example ships with 3 pre-configured Graphman repositories. The repository controller is responsible for synchronising these with the Operator and should always be created before Gateway resources that reference them to avoid race conditions. ***race conditions will be resolved automatically.*** - -- [l7-gw-myframework](https://github.com/Gazza7205/l7GWMyFramework) -- [l7-gw-mysubscriptions](https://github.com/Gazza7205/l7GWMySubscriptions) -- [l7-gw-myapis](https://github.com/Gazza7205/l7GWMyAPIs) - -``` -kubectl apply -k ./example/repositories -``` - -#### View the Operator Logs -``` -kubectl logs -f -l app.kubernetes.io/name=layer7-operator -``` - -#### Repository CR -The Repository Controller keeps tracks the latest available commit, where it's stored (if it's less than 1mb we create a Kubernetes secret) and when it was last updated. The Storage Secret is a gzipped graphman bundle (json) used in the Graphman Init Container to remove dependencies on git during deployment. - -***Note: If the repository exceeds 1mb in compressed format each Graphman Init Container will clone it at runtime. This represents a single point of failure if your Git Server is down, we recommended creating your own initContainer with the larger graphman bundle.*** -``` -kubectl get repositories - -NAME AGE -l7-gw-myapis 10s -l7-gw-myframework 10s -l7-gw-mysubscriptions 10s - -kubectl get repository l7-gw-myapis -oyaml -... -status: - commit: 3791f11c9b588b383ce87535f46d4fc1526ae83b - name: l7-gw-myapis - storageSecretName: l7-gw-myapis-repository - updated: 2023-04-04 02:53:53.298060678 +0000 UTC m=+752.481758238 - vendor: Github -``` - -### Create a Gateway -``` -kubectl apply -f ./gateway/otel-prometheus-gateway.yaml -``` - -#### Referencing the repositories we created -[otel-prometheus-gateway.yaml](../gateway/otel-prometheus-gateway.yaml) contains 3 repository references, the 'type' defines how a repository is applied to the Container Gateway. -- Dynamic repositories are applied directly to the Graphman endpoint on the Gateway which does not require a gateway restart -- Static repositories are bootstrapped to the Container Gateway with an initContainer which requires a gateway restart. -``` -repositoryReferences: - - name: l7-gw-myframework - enabled: true - type: ***static*** - encryption: - existingSecret: graphman-encryption-secret - key: FRAMEWORK_ENCRYPTION_PASSPHRASE - - name: l7-gw-myapis - enabled: true - type: ***dynamic*** - encryption: - existingSecret: graphman-encryption-secret - key: APIS_ENCRYPTION_PASSPHRASE - - name: l7-gw-mysubscriptions - enabled: true - type: ***dynamic*** - encryption: - existingSecret: graphman-encryption-secret - key: SUBSCRIPTIONS_ENCRYPTION_PASSPHRASE -``` - -#### View your new Gateway -``` -kubectl get pods - -NAME READY STATUS RESTARTS AGE -layer7-operator-controller-manager-7647b58697-qd9vg 2/2 Running 0 15m -ssg-57d96567cb-n24g9 2/2 Running 0 73s -``` - -#### Static Graphman Repositories -Because we created the l7-gw-myframework repository reference with type 'static' the Layer7 Operator automatically injects an initContainer to bootstrap the repository to the Container Gateway. -Note: the suffix here graphman-static-init-***c1b58adb6d*** is generated using all static commit ids, if a static repository changes the Gateway will be updated. -``` -kubectl describe pods ssg-57d96567cb-n24g9 - -... -Init Containers: - graphman-static-init-c1b58adb6d: - Container ID: containerd://21924ae85d25437d3634ea5da1415c9bb58d678600f9fd67d4f0b0360857d7c5 - Image: docker.io/caapim/graphman-static-init:1.0.4 - Image ID: docker.io/caapim/graphman-static-init@sha256:8cb1035035b18fa9dc2c95e2b584c758e78909b3f615ee5f49dce166e8aae213 - Port: - Host Port: - State: Terminated - Reason: Completed - Exit Code: 0 - Started: Tue, 04 Apr 2023 04:11:18 +0100 - Finished: Tue, 04 Apr 2023 04:11:18 +0100 -... -``` -#### View the Graphman InitContainer logs -We should see that our static repository l7-gw-myframework has been picked up and moved to the bootstrap folder. -``` -kubectl logs ssg-57d96567cb-n24g9 graphman-static-init-c1b58adb6d - -l7-gw-myframework with 40kbs written to /opt/SecureSpan/Gateway/node/default/etc/bootstrap/bundle/graphman/0/0_l7-gw-myframework.json -``` - -#### View the Operator logs -``` -kubectl logs -f -l app.kubernetes.io/name=layer7-operator -``` - -#### Inspect the Status of your Custom Resources - -#### Gateway CR -The Gateway Controller tracks gateway pods and the repositories that have been applied to the deployment -``` -kubectl get gateway ssg -oyaml - -status: - ... - gateway: - - name: ssg-6b7d7fd999-n5bsj - phase: Running - ready: true - startTime: 2023-04-03 18:57:24 +0000 UTC - host: gateway.brcmlabs.com - image: caapim/gateway:11.1.3 - ready: 1 - replicas: 1 -repositoryStatus: -- branch: main - commit: c93028b807cf1b62bce0142a80ad4f6203207e8d - enabled: true - endpoint: https://github.com/Gazza7205/l7GWMyFramework - name: l7-gw-myframework - secretName: graphman-repository-secret - storageSecretName: l7-gw-myframework-repository - type: static -- branch: main - commit: 3791f11c9b588b383ce87535f46d4fc1526ae83b - enabled: true - endpoint: https://github.com/Gazza7205/l7GWMyAPIs - name: l7-gw-myapis - secretName: graphman-repository-secret - storageSecretName: l7-gw-myapis-repository - type: dynamic -- branch: main - commit: fd6b225159fcd8fccf4bd61e31f40cdac64eccfa - enabled: true - endpoint: https://github.com/Gazza7205/l7GWMySubscriptions - name: l7-gw-mysubscriptions - secretName: graphman-repository-secret - storageSecretName: l7-gw-mysubscriptions-repository - type: dynamic -state: Ready -version: 11.1.3 -``` - -#### Repository CR -The Repository Controller keeps tracks the latest available commit, where it's stored (if it's less than 1mb we create a Kubernetes secret) and when it was last updated. -``` -kubectl get repository l7-gw-myapis -oyaml -... -status: - commit: 7332f861e11612a91ca9de6b079826b9377dae6a - name: l7-gw-myapis - storageSecretName: l7-gw-myapis-repository - updated: 2023-04-06 15:00:20.144406434 +0000 UTC m=+21.758241719 - vendor: Github -``` - -### Test your Gateway Deployment -``` -kubectl get ingress - -NAME CLASS HOSTS ADDRESS PORTS AGE -ssg nginx gateway.brcmlabs.com 34.89.126.80 80, 443 54m -``` - -Add the following to your hosts file for DNS resolution -``` -Format -$ADDRESS $HOST - -example -34.89.126.80 gateway.brcmlabs.com -``` -Curl -``` -curl https://gateway.brcmlabs.com/api1 -H "client-id: D63FA04C8447" -k -``` -Response -``` -{ - "client" : "D63FA04C8447", - "plan" : "plan_a", - "service" : "hello api 1", - "myDemoConfigVal" : "Mondays" -} -``` - -#### Sign into Policy Manager -Policy Manager access is less relevant in a deployment like this because we haven't specified an external MySQL database, any changes that we make will only apply to the Gateway that we're connected to and won't survive a restart. It is still useful to check what's been applied. We configured custom ports where we disabled Policy Manager access on 8443, we're also using an ingress controller meaning that port 9443 is not accessible without port forwarding. - -Port-Forward -``` -kubectl get pods -NAME READY STATUS RESTARTS AGE -... -ssg-7698bc565b-szrbj 1/1 Running 0 54m - -kubectl port-forward ssg-7698bc565b-szrbj 9443:9443 -``` -Policy Manager -``` -username: admin -password: 7layer -gateway: localhost:9443 -``` - -#### Access Jaeger -Jaeger has been configured via ingress which should resolve to https://jaeger.brcmlabs.com. You can use Jaeger to view assertion level traces of the test policy we just tested with curl. - -![jaeger](../images/jaeger.gif) - - -#### Access Grafana -Grafana has been configured via ingress which should resolve to https://grafana.brcmlabs.com. You can use Grafana to view Gateway service level + general metrics of your Gateway Deployment. - -``` -username: admin -password: 7layer -``` -![grafana](../images/grafana.gif) - - -### Remove Kind Cluster -If you used the Quickstart option and deployed Kind, all you will need to do is remove the Kind Cluster. - -Make sure that you're in the example folder -``` -pwd -``` - -output -``` -/path/to/layer7-operator/example -``` - -Remove the Kind Cluster -``` -make uninstall-kind -``` - -### Remove Demo Component and Custom Resources -``` -kubectl delete -f ./example/gateway/otel-prometheus-gateway.yaml -kubectl delete -k ./example/repositories/ -kubectl delete -k ./example/otel-prometheus/monitoring/grafana -kubectl delete -f ./example/otel-prometheus/servicemonitor.yaml -kubectl delete -f ./example/otel-prometheus/collector.yaml -kubectl delete -f ./example/otel-prometheus/instrumentation.yaml -kubectl delete -f ./example/otel-prometheus/observability/jaeger/jaeger.yaml -kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml -kubectl delete -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.146.0/opentelemetry-operator.yaml -kubectl delete -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.65.0/jaeger-operator.yaml -n observability -kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.0/cert-manager.yaml -helm uninstall prometheus -n monitoring -kubectl delete ns monitoring -kubectl delete ns observability -``` - -### Uninstall the Operator -``` -kubectl delete -f https://github.com/CAAPIM/layer7-operator/releases/download/v1.2.2/bundle.yaml -``` \ No newline at end of file diff --git a/example/otel-prometheus/collector.yaml b/example/otel-prometheus/collector.yaml deleted file mode 100644 index 581e7cde..00000000 --- a/example/otel-prometheus/collector.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: opentelemetry.io/v1alpha1 -kind: OpenTelemetryCollector -metadata: - name: ssg-prom -spec: - image: otel/opentelemetry-collector-contrib:0.97.0 - mode: sidecar - config: | - receivers: - otlp: - protocols: - grpc: - endpoint: 0.0.0.0:4317 - http: - endpoint: 0.0.0.0:4318 - processors: - batch: - exporters: - debug: - verbosity: basic - prometheus: - endpoint: "0.0.0.0:8889" - const_labels: - name: ssg - otlp: - endpoint: simple-allinone-collector:4317 - tls: - insecure: true - service: - telemetry: - logs: - level: "debug" - metrics: - address: "0.0.0.0:8888" - pipelines: - traces: - receivers: [otlp] - processors: [batch] - exporters: [otlp] - metrics: - receivers: [otlp] - processors: [batch] - exporters: [prometheus, debug] - logs: - receivers: [otlp] - exporters: [debug] - extensions: - health_check: - pprof: - endpoint: 0.0.0.0:1777 - zpages: - endpoint: 0.0.0.0:55679 \ No newline at end of file diff --git a/example/otel-prometheus/instrumentation.yaml b/example/otel-prometheus/instrumentation.yaml deleted file mode 100644 index f79b8745..00000000 --- a/example/otel-prometheus/instrumentation.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: opentelemetry.io/v1alpha1 -kind: Instrumentation -metadata: - name: otel-instrumentation -spec: - env: - - name: OTEL_SERVICE_NAME - value: ssg - - name: OTEL_METRICS_EXPORTER - value: otlp - - name: OTEL_TRACES_EXPORTER - value: otlp - - name: OTEL_RESOURCE_ATTRIBUTES - value: service.version=11.0.00_CR1,deployment.environment=development - exporter: - endpoint: http://localhost:4317 - propagators: - - tracecontext - - baggage - - b3 - sampler: - type: parentbased_traceidratio - argument: "0.25" \ No newline at end of file diff --git a/example/otel-prometheus/monitoring/grafana/kustomization.yaml b/example/otel-prometheus/monitoring/grafana/kustomization.yaml deleted file mode 100644 index 535b9faa..00000000 --- a/example/otel-prometheus/monitoring/grafana/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: monitoring -generatorOptions: - disableNameSuffixHash: true -configMapGenerator: - - name: layer7-gateway-dashboard - files: - - "./layer7-gateway-dashboard.json" - diff --git a/example/otel-prometheus/monitoring/grafana/layer7-gateway-dashboard.json b/example/otel-prometheus/monitoring/grafana/layer7-gateway-dashboard.json deleted file mode 100644 index eb76e807..00000000 --- a/example/otel-prometheus/monitoring/grafana/layer7-gateway-dashboard.json +++ /dev/null @@ -1,1414 +0,0 @@ -{ - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "links": [], - "liveNow": false, - "panels": [ - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 18, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "refId": "A" - } - ], - "title": "Gateway Service Metrics", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 0, - "y": 1 - }, - "id": 3, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(l7_service_attempted_count_total{job=\"$gatewayDeployment\"})", - "instant": false, - "interval": "", - "legendFormat": "{{podName}}", - "refId": "A" - } - ], - "title": "Total Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 3, - "y": 1 - }, - "id": 12, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(l7_service_success_count_total{job=\"$gatewayDeployment\"})", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Successful Requests", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 3, - "x": 6, - "y": 1 - }, - "id": 13, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum(l7_service_attempted_count_total{job=\"$gatewayDeployment\"} - l7_service_success_count_total{job=\"$gatewayDeployment\"})", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "Routing Failures", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 15, - "x": 9, - "y": 1 - }, - "id": 8, - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "7.5.11", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": false, - "expr": "sum(rate(l7_service_attempted_count_total{job=\"$gatewayDeployment\"}[5m]))", - "instant": false, - "interval": "", - "legendFormat": "${gatewayDeployment}", - "refId": "A" - } - ], - "title": "Transaction Rate (Cluster[5m])", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "serviceUri" - }, - "properties": [ - { - "id": "custom.width", - "value": 218 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Service Name" - }, - "properties": [ - { - "id": "custom.width", - "value": 282 - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 9, - "x": 0, - "y": 6 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [] - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "l7_service_policy_violations_count_total{job=\"$gatewayDeployment\"}", - "format": "table", - "instant": true, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{apiName}}", - "refId": "A" - } - ], - "title": "Policy Violations", - "transformations": [ - { - "id": "organize", - "options": { - "excludeByName": { - "Time": true, - "__name__": true, - "container": true, - "endpoint": true, - "exported_job": true, - "goid": true, - "instance": true, - "job": true, - "l7_goid": true, - "l7_method": false, - "name": true, - "namespace": true, - "pod": false, - "service": true, - "serviceUri": true - }, - "includeByName": {}, - "indexByName": {}, - "renameByName": { - "Value": "Count", - "__name__": "", - "apiName": "Service Name", - "container": "", - "l7_method": "Method", - "l7_serviceName": "Service Name", - "l7_serviceUri": "Service URI", - "namespace": "", - "pod": "Pod", - "service": "" - } - } - } - ], - "type": "table" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "graph": false, - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 15, - "x": 9, - "y": 8 - }, - "id": 7, - "options": { - "graph": {}, - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "7.5.11", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": false, - "expr": "rate(l7_service_attempted_count_total{l7_serviceName=\"$serviceName\",job=\"$gatewayDeployment\",l7_method=\"$requestMethod\"}[5m])", - "instant": false, - "interval": "", - "legendFormat": "{{l7_serviceName}}", - "refId": "A" - } - ], - "title": "Transactions Rate (Service[5m])", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - }, - "unit": "ms" - }, - "overrides": [] - }, - "gridPos": { - "h": 5, - "w": 9, - "x": 0, - "y": 12 - }, - "id": 15, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "text": {}, - "textMode": "auto", - "wideLayout": true - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "l7_service_latency_milliseconds_sum{job=\"$gatewayDeployment\"} / l7_service_latency_milliseconds_count{job=\"$gatewayDeployment\"}", - "interval": "", - "legendFormat": "{{pod}}-{{l7_serviceName}}", - "range": true, - "refId": "A" - } - ], - "title": "Avg. Latency (milliseconds) $serviceName", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "description": "", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "fillOpacity": 80, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineWidth": 1 - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "ms" - }, - "overrides": [] - }, - "gridPos": { - "h": 7, - "w": 15, - "x": 9, - "y": 16 - }, - "id": 29, - "options": { - "bucketOffset": 0, - "combine": false, - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - } - }, - "pluginVersion": "10.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": false, - "expr": "histogram_quantile(0.95, l7_service_latency_milliseconds_bucket{})", - "format": "time_series", - "instant": false, - "legendFormat": "{{l7_serviceName}}", - "range": true, - "refId": "A" - } - ], - "title": "Service Latency Histogram", - "type": "histogram" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "left", - "cellOptions": { - "type": "auto" - }, - "filterable": false, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Request URI" - }, - "properties": [ - { - "id": "custom.width", - "value": 160 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Service Name" - }, - "properties": [ - { - "id": "custom.width", - "value": 124 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "Pod" - }, - "properties": [ - { - "id": "custom.width", - "value": 206 - } - ] - }, - { - "matcher": { - "id": "byName", - "options": "request_method" - }, - "properties": [ - { - "id": "custom.width", - "value": 125 - } - ] - } - ] - }, - "gridPos": { - "h": 6, - "w": 9, - "x": 0, - "y": 17 - }, - "id": 6, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": false, - "displayName": "Pod" - } - ] - }, - "pluginVersion": "10.4.1", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": false, - "expr": "l7_service_attempted_count_total{job=\"$gatewayDeployment\", l7_method=\"$requestMethod\"}", - "format": "table", - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Requests per service", - "transformations": [ - { - "id": "filterFieldsByName", - "options": { - "include": { - "names": [ - "l7_method", - "l7_serviceName", - "l7_serviceUri", - "Value", - "pod" - ] - } - } - }, - { - "id": "organize", - "options": { - "excludeByName": {}, - "includeByName": {}, - "indexByName": {}, - "renameByName": { - "Value": "Count", - "exported_job": "", - "l7_method": "Method", - "l7_serviceName": "Service Name", - "l7_serviceUri": "Service URI", - "pod": "Pod" - } - } - } - ], - "type": "table" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 23 - }, - "id": 20, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "refId": "A" - } - ], - "title": "Gateway Application Metrics", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 24 - }, - "id": 22, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "10.0.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "process_runtime_jvm_system_cpu_utilization_ratio", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "CPU Utilization", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 24 - }, - "id": 24, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "10.0.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "process_runtime_jvm_system_cpu_load_1m", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" - } - ], - "title": "System Load (1m)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 32 - }, - "id": 23, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "10.0.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "process_runtime_jvm_memory_usage_bytes{type=\"non_heap\"}", - "interval": "", - "legendFormat": "{{pod}}-{{pool}}-{{type}}", - "range": true, - "refId": "A" - } - ], - "title": "Memory Usage (non-heap)", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "decbytes" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 32 - }, - "id": 25, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "10.0.3", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "mimir" - }, - "editorMode": "code", - "exemplar": true, - "expr": "process_runtime_jvm_memory_usage_bytes{type=\"heap\"}", - "interval": "", - "legendFormat": "{{pod}}-{{pool}}-{{type}}", - "range": true, - "refId": "A" - } - ], - "title": "Memory Usage (heap)", - "type": "timeseries" - } - ], - "refresh": "5s", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [ - { - "current": { - "selected": false, - "text": "ssg", - "value": "ssg" - }, - "definition": "label_values(l7_service_attempted_count_total,job)", - "hide": 0, - "includeAll": false, - "label": "Gateway Deployment", - "multi": false, - "name": "gatewayDeployment", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(l7_service_attempted_count_total,job)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "ssg-5b88cc7874-5jjlm", - "value": "ssg-5b88cc7874-5jjlm" - }, - "definition": "label_values(l7_service_attempted_count_total{pod!=\"\", job=\"$gatewayDeployment\"},pod)", - "hide": 0, - "includeAll": false, - "label": "Pod", - "multi": false, - "name": "gatewayPod", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(l7_service_attempted_count_total{pod!=\"\", job=\"$gatewayDeployment\"},pod)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "Gateway GraphQL Management Service", - "value": "Gateway GraphQL Management Service" - }, - "definition": "label_values(l7_service_attempted_count_total{job=\"$gatewayDeployment\", pod=\"$gatewayPod\"},l7_serviceName)", - "hide": 0, - "includeAll": false, - "label": "Service Name", - "multi": false, - "name": "serviceName", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(l7_service_attempted_count_total{job=\"$gatewayDeployment\", pod=\"$gatewayPod\"},l7_serviceName)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - }, - { - "current": { - "selected": false, - "text": "POST", - "value": "POST" - }, - "definition": "label_values(l7_service_attempted_count_total{job=\"$gatewayDeployment\", l7_serviceName=\"$serviceName\"},l7_method)", - "hide": 0, - "includeAll": false, - "label": "Request Method", - "multi": false, - "name": "requestMethod", - "options": [], - "query": { - "qryType": 1, - "query": "label_values(l7_service_attempted_count_total{job=\"$gatewayDeployment\", l7_serviceName=\"$serviceName\"},l7_method)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-5m", - "to": "now" - }, - "timepicker": {}, - "timezone": "", - "title": "Gateway Dashboard", - "uid": "K5qg2_14k", - "version": 1, - "weekStart": "" -} \ No newline at end of file diff --git a/example/otel-prometheus/monitoring/prometheus/prometheus-values.yaml b/example/otel-prometheus/monitoring/prometheus/prometheus-values.yaml deleted file mode 100644 index 97fb7668..00000000 --- a/example/otel-prometheus/monitoring/prometheus/prometheus-values.yaml +++ /dev/null @@ -1,80 +0,0 @@ -prometheusOperator: - namespaces: - releaseNamespace: true - additional: - - kube-system - - layer7 - - default - denyNamespaces: [] - prometheusInstanceNamespaces: [] - alertmanagerInstanceNamespaces: [] - thanosRulerInstanceNamespaces: [] - -prometheus: - prometheusSpec: - serviceMonitorSelectorNilUsesHelmValues: false - serviceMonitorSelector: {} - serviceMonitorNamespaceSelector: {} - -grafana: - enabled: true - dashboardProviders: - dashboardproviders.yaml: - apiVersion: 1 - providers: - - name: 'layer7' - orgId: 1 - folder: 'Layer7' - type: file - disableDeletion: true - editable: true - options: - path: /var/lib/grafana/dashboards/layer7 - - - dashboardsConfigMaps: - layer7: layer7-gateway-dashboard - - adminPassword: 7layer - - rbac: - ## If true, Grafana PSPs will be created - ## - pspEnabled: false - - ingress: - ## If true, Grafana Ingress will be created - ## - enabled: true - - ## IngressClassName for Grafana Ingress. - ## Should be provided if Ingress is enable. - ## - ingressClassName: nginx - - ## Annotations for Grafana Ingress - ## - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - - ## Labels to be added to the Ingress - ## - labels: {} - - ## Hostnames. - ## Must be provided if Ingress is enable. - ## - hosts: - - grafana.brcmlabs.com - - ## Path for grafana ingress - path: / - - ## TLS configuration for grafana Ingress - ## Secret must be manually created in the namespace - ## - tls: - - secretName: brcmlabs - hosts: - - grafana.brcmlabs.com \ No newline at end of file diff --git a/example/otel-prometheus/observability/jaeger/ingress.yaml b/example/otel-prometheus/observability/jaeger/ingress.yaml deleted file mode 100644 index b65d3cc5..00000000 --- a/example/otel-prometheus/observability/jaeger/ingress.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: jaeger-ingress - annotations: {} -spec: - ingressClassName: nginx - rules: - - host: jaeger.brcmlabs.com - http: - paths: - - backend: - service: - name: simple-allinone-query - port: - number: 16686 - path: / - pathType: Prefix - tls: - - hosts: - - jaeger.brcmlabs.com - secretName: default \ No newline at end of file diff --git a/example/otel-prometheus/observability/jaeger/jaeger.yaml b/example/otel-prometheus/observability/jaeger/jaeger.yaml deleted file mode 100644 index d1a87f41..00000000 --- a/example/otel-prometheus/observability/jaeger/jaeger.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: jaegertracing.io/v1 -kind: Jaeger -metadata: - name: simple-allinone -spec: - ingress: - secretName: default \ No newline at end of file diff --git a/example/otel-prometheus/servicemonitor.yaml b/example/otel-prometheus/servicemonitor.yaml deleted file mode 100644 index 8bf21623..00000000 --- a/example/otel-prometheus/servicemonitor.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - app: ssg - name: ssg -spec: - endpoints: - - interval: 10s - path: /metrics - port: monitoring - jobLabel: ssg - namespaceSelector: - matchNames: - - default - selector: - matchLabels: - app.kubernetes.io/created-by: layer7-operator - app.kubernetes.io/managed-by: layer7-operator - app.kubernetes.io/name: ssg - app.kubernetes.io/part-of: ssg diff --git a/example/otk/single/README.md b/example/otk/single/README.md index b525c17a..1805ab9c 100644 --- a/example/otk/single/README.md +++ b/example/otk/single/README.md @@ -13,15 +13,15 @@ By the end of this example you should have a better understanding of the Layer7 ``` 3. If you would like to create a TLS secret for your ingress controller then add tls.crt and tls.key to [base/resources/secrets/tls](../base/resources/secrets/tls) - these will be referenced later on. -4. You will need an ingress controller like nginx +4. You will need an ingress controller like contour - if you do not have one installed already you can use the makefile in the example directory to deploy one - ```cd example``` - Generic Kubernetes - - ```make nginx``` + - ```make contour``` - Kind (Kubernetes in Docker) - follow the steps in Quickstart or - - ```make nginx-kind``` + - ```make contour-kind``` - return to the previous folder - ```cd ..``` @@ -38,11 +38,11 @@ cd example ``` make kind-cluster ``` -3. Deploy Nginx (based on your environment) +3. Deploy Contour (based on your environment) ``` -make nginx +make contour or -make nginx-kind +make contour-kind ``` 4. Deploy the example ``` @@ -382,8 +382,8 @@ Take note of your access_token, you will use that in the next step ``` kubectl get ingress -NAME CLASS HOSTS ADDRESS PORTS AGE -ssg nginx gateway.brcmlabs.com,gateway-otk-management.brcmlabs.com or localhost 80, 443 54m +NAME CLASS HOSTS ADDRESS PORTS AGE +ssg contour gateway.brcmlabs.com,gateway-otk-management.brcmlabs.com or localhost 80, 443 54m ``` Add the following to your hosts file for DNS resolution diff --git a/example/portal-integration/portal-values.yaml b/example/portal-integration/portal-values.yaml index 2a672658..6f7b2098 100644 --- a/example/portal-integration/portal-values.yaml +++ b/example/portal-integration/portal-values.yaml @@ -17,16 +17,15 @@ portal: ingress: class: - name: nginx + name: contour enabled: true type: - kubernetes: true + kubernetes: false openshift: false + contour: true secretName: dispatcher-tls create: true - annotations: - nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" - nginx.ingress.kubernetes.io/ssl-passthrough: "true" + annotations: {} tenantIds: - portal diff --git a/example/portal-integration/readme.md b/example/portal-integration/readme.md index 2519d69b..fcb03f43 100644 --- a/example/portal-integration/readme.md +++ b/example/portal-integration/readme.md @@ -32,19 +32,6 @@ By the end of this example you should have a better understanding of the Layer7 ``` ***NOTE*** If you are using an existing Kubernetes Cluster you can retrieve the correct address after the Prometheus Stack has been deployed - ``` - kubectl get ingress - ``` - output - ``` - NAME CLASS HOSTS ADDRESS PORTS AGE - portal-ingress nginx apim-dev-portal.brcmlabs.com,dev-portal-ssg.brcmlabs.com,dev-portal-analytics.brcmlabs.com + 5 more... 80, 443 57m - ``` - In your hosts file - the ingress address will be the same for the Gateway Ingress record - ``` - gateway.brcmlabs.com portal.brcmlabs.com apim-dev-portal.brcmlabs.com dev-portal-ssg.brcmlabs.com dev-portal-enroll.brcmlabs.com dev-portal-sync.brcmlabs.com dev-portal-sso.brcmlabs.com dev-portal-analytics.brcmlabs.com dev-portal-broker.brcmlabs.com - ``` - - We recommend sticking with the defaults to try out this experimental example as they are used to provision a Portal Tenant - If you wish to change the default you can do so in [portal-values.yaml](../portal-integration/portal-values.yaml) - set portal.domain @@ -56,20 +43,16 @@ By the end of this example you should have a better understanding of the Layer7 ``` - Your hosts file will need to use yourportaldomain.com in place of brcmlabs.com - The [enroll-payload](./enroll-payload.json) will also need to be updated -6. You will need an ingress controller like nginx +6. You will need an ingress controller like contour - if you do not have one installed already you can use the makefile in the example directory to deploy one - ```cd example``` - Generic Kubernetes - - ```make nginx``` + - ```make contour``` - Kind (Kubernetes in Docker) - follow the steps in [Quickstart](#quickstart) or - - ```make nginx-kind``` + - ```make contour-kind``` - **NOTE:** the Portal requires an ingress controller that supports ssl/tls passthrough for mutual ssl/tls. - - This will add the following [command line argument](https://kubernetes.github.io/ingress-nginx/user-guide/cli-arguments/) to nginx, in the ingress-nginx namespace. **It has only been tested** deployed with the above commands - - ```--enable-ssl-passthrough``` - - The following command will edit your nginx deployment - - ```make configure-nginx-ssl-passthrough``` ### Guide @@ -82,7 +65,7 @@ export NAMESPACE=yournamespace kubectl config set-context --current --namespace=yournamespace ``` -If you deploy the nginx ingress controller as part of this example, it will use a namespace called ingress-nginx +If you deploy the contour ingress controller as part of this example, it will use a namespace called projectcontour * [Quickstart](#quickstart) * [Using an existing Kubernetes Cluster](#existing-kubernetes-cluster) @@ -146,17 +129,17 @@ cd example ### Kind ``` -make kind-cluster nginx-kind configure-nginx-ssl-passthrough portal-example +make kind-cluster contour-kind portal-example ``` ### Existing Kubernetes Cluster -An ingress controller is required for this example, if you don't have an ingress controller you can deploy nginx with the following +An ingress controller is required for this example, if you don't have an ingress controller you can deploy contour with the following ``` -make nginx configure-nginx-ssl-passthrough +make contour ``` if you are using kind ``` -make nginx-kind configure-nginx-ssl-passthrough +make contour-kind ``` Deploy the example components ``` @@ -215,19 +198,7 @@ Enrollment key retrieved 3. Open your tenant gateway and enroll this gateway with the portal using the URL from step 2. ``` **Note** if this step fails, there are two likely scenarios -1. You have not configured nginx for ssl/tls passthrough. Please see step 4 in [getting-started](#getting-started) -2. You have not configured your local hosts file. Please see step 6 in [getting-started](#getting-started) - - - +1. You have not configured your local hosts file. Please see step 6 in [getting-started](#getting-started) ### Deploy the Layer7 Operator This integration example uses v1.2.2 of the Layer7 Operator From 0f54d5bf7f1fbed6f8b57ac520d475508a0bfb11 Mon Sep 17 00:00:00 2001 From: Gary Vermeulen Date: Tue, 24 Mar 2026 08:05:13 +0000 Subject: [PATCH 10/11] removing schedulers for dual-mode OTK --- api/v1/gateway_types.go | 48 +- api/v1/zz_generated.deepcopy.go | 17 + .../bases/security.brcmlabs.com_gateways.yaml | 89 +- internal/controller/gateway/controller.go | 16 +- pkg/gateway/reconcile/cron.go | 115 --- pkg/gateway/reconcile/externalkeys.go | 533 ---------- pkg/gateway/reconcile/gateway.go | 7 +- pkg/gateway/reconcile/l7otkcertificates.go | 950 ------------------ pkg/gateway/reconcile/l7otkpolicies.go | 124 --- 9 files changed, 97 insertions(+), 1802 deletions(-) delete mode 100644 pkg/gateway/reconcile/cron.go delete mode 100644 pkg/gateway/reconcile/l7otkcertificates.go delete mode 100644 pkg/gateway/reconcile/l7otkpolicies.go diff --git a/api/v1/gateway_types.go b/api/v1/gateway_types.go index 646d9929..c184daf1 100644 --- a/api/v1/gateway_types.go +++ b/api/v1/gateway_types.go @@ -342,6 +342,9 @@ type PortalReference struct { type Otk struct { // Enable or disable the OTK initContainer Enabled bool `json:"enabled,omitempty"` + // ManageCrossNamespace allows a cluster-wide layer7 operator to manage internal/dmz gateways across namespaces + // this is limited to a single kubernetes cluster. + ManageCrossNamespace bool `json:"manageCrossNamespace,omitempty"` // InitContainerImage for the initContainer InitContainerImage string `json:"initContainerImage,omitempty"` // InitContainerImagePullPolicy @@ -356,36 +359,14 @@ type Otk struct { Overrides OtkOverrides `json:"overrides,omitempty"` // A list of subSolutionKitNames - all,internal or dmz cover the primary use cases for the OTK. Only use if directed by support SubSolutionKitNames []string `json:"subSolutionKitNames,omitempty"` - // InternalOtkGatewayReference to an Operator managed Gateway deployment that is configured with otk.type: internal - // This configures a relationship between DMZ and Internal Gateways. - InternalOtkGatewayReference string `json:"internalGatewayReference,omitempty"` - // InternalGatewayPort defaults to 9443 or graphmanDynamicSync port - // This port is used when the Internal gateway is external (not managed by operator) - InternalGatewayPort int `json:"internalGatewayPort,omitempty"` - // OTKPort is used in Single mode - sets the otk.port cluster-wide property and in Dual-Mode - // sets host_oauth2_auth_server port in #OTK Client Context Variables - // TODO: Make this an array for many dmz deployments to one internal - DmzOtkGatewayReference string `json:"dmzGatewayReference,omitempty"` - // DmzGatewayPort defaults to 9443 or graphmanDynamicSync port - // This port is used when the DMZ gateway is external (not managed by operator) - DmzGatewayPort int `json:"dmzGatewayPort,omitempty"` // OTKPort defaults to 8443 OTKPort int `json:"port,omitempty"` // MaintenanceTasks for the OTK database are disabled by default MaintenanceTasks OtkMaintenanceTasks `json:"maintenanceTasks,omitempty"` - // RuntimeSyncIntervalSeconds how often OTK Gateways should be updated in internal/dmz mode - RuntimeSyncIntervalSeconds int `json:"runtimeSyncIntervalSeconds,omitempty"` - // SyncIntervalSeconds determines how often DMZ and Internal gateways should update certificates - // Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set - SyncIntervalSeconds int `json:"syncIntervalSeconds,omitempty"` - // DmzKeySecret is a reference to a kubernetes.io/tls Secret containing the DMZ private key and certificate - DmzKeySecret string `json:"dmzKeySecret,omitempty"` - // InternalKeySecret is a reference to a kubernetes.io/tls Secret containing the Internal private key and certificate - InternalKeySecret string `json:"internalKeySecret,omitempty"` - // DmzAuthSecret is a reference to a Secret containing username and password for DMZ authentication - DmzAuthSecret string `json:"dmzAuthSecret,omitempty"` - // InternalAuthSecret is a reference to a Secret containing username and password for Internal authentication - InternalAuthSecret string `json:"internalAuthSecret,omitempty"` + //InternalOTKGateway reference if type is dmz + InternalOTKGateway GatewayReference `json:"internalGateway,omitempty"` + //DmzOTKGateway reference if type is internal + DmzOTKGateway GatewayReference `json:"dmzGateway,omitempty"` } // OtkMaintenanceTasks are included in the install bundle as disabled scheduled tasks @@ -395,6 +376,19 @@ type OtkMaintenanceTasks struct { Enabled bool `json:"enabled,omitempty"` } +type GatewayReference struct { + // Name of the gateway + // if managing otk gateways across namespaces this must match the referenced gateway CR + Name string `json:"name,omitempty"` + // Namespace of the referenced gateway if managing gateways cross namespace (optional) + Namespace string `json:"namespace,omitempty"` + // Url of the target gateway + // used for post-installation gateway policy configuration + Url string `json:"Url,omitempty"` + // Port of the target gateway + Port int `json:"port,omitempty"` +} + type OtkOverrides struct { // Enable or disable otk overrides Enabled bool `json:"enabled,omitempty"` @@ -909,7 +903,7 @@ type ExternalKey struct { // SSL | CA | AUDIT_SIGNING | AUDIT_VIEWER KeyUsageType KeyUsageType `json:"keyUsageType,omitempty"` // Otk indicates that this key usage was specific for OTK - Otk bool `json:"otk,omitempty"` + //Otk bool `json:"otk,omitempty"` } type KeyUsageType string diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 3fec98ec..e1dac776 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -570,6 +570,21 @@ func (in *GatewayList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GatewayReference) DeepCopyInto(out *GatewayReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayReference. +func (in *GatewayReference) DeepCopy() *GatewayReference { + if in == nil { + return nil + } + out := new(GatewayReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayRepositoryStatus) DeepCopyInto(out *GatewayRepositoryStatus) { *out = *in @@ -1035,6 +1050,8 @@ func (in *Otk) DeepCopyInto(out *Otk) { copy(*out, *in) } out.MaintenanceTasks = in.MaintenanceTasks + out.InternalOTKGateway = in.InternalOTKGateway + out.DmzOTKGateway = in.DmzOTKGateway } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Otk. diff --git a/config/crd/bases/security.brcmlabs.com_gateways.yaml b/config/crd/bases/security.brcmlabs.com_gateways.yaml index 0ebb6746..34d4b9ce 100644 --- a/config/crd/bases/security.brcmlabs.com_gateways.yaml +++ b/config/crd/bases/security.brcmlabs.com_gateways.yaml @@ -1589,10 +1589,6 @@ spec: description: Name of the kubernetes.io/tls Secret which already exists in Kubernetes type: string - otk: - description: Otk indicates that this key usage was specific - for OTK - type: boolean type: object type: array externalSecrets: @@ -4007,21 +4003,27 @@ spec: description: Type of OTK Database type: string type: object - dmzAuthSecret: - description: DmzAuthSecret is a reference to a Secret containing - username and password... - type: string - dmzGatewayPort: - description: |- - DmzGatewayPort defaults to 9443 or graphmanDynamicSync port - This port is... - type: integer - dmzGatewayReference: - description: OTKPort is used in Single mode - sets the otk. - type: string - dmzKeySecret: - description: DmzKeySecret is a reference to a kubernetes. - type: string + dmzGateway: + description: DmzOTKGateway reference if type is internal + properties: + Url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + type: object enabled: description: Enable or disable the OTK initContainer type: boolean @@ -4158,22 +4160,27 @@ spec: type: string type: object type: object - internalAuthSecret: - description: InternalAuthSecret is a reference to a Secret - containing username and... - type: string - internalGatewayPort: - description: |- - InternalGatewayPort defaults to 9443 or graphmanDynamicSync port - This port... - type: integer - internalGatewayReference: - description: InternalOtkGatewayReference to an Operator managed - Gateway deployment that... - type: string - internalKeySecret: - description: InternalKeySecret is a reference to a kubernetes. - type: string + internalGateway: + description: InternalOTKGateway reference if type is dmz + properties: + Url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + type: object maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled by default @@ -4182,6 +4189,10 @@ spec: description: Enable or disable database maintenance tasks type: boolean type: object + manageCrossNamespace: + description: ManageCrossNamespace allows a cluster-wide layer7 + operator to manage... + type: boolean overrides: description: Overrides default OTK install functionality properties: @@ -4216,20 +4227,12 @@ spec: port: description: OTKPort defaults to 8443 type: integer - runtimeSyncIntervalSeconds: - description: RuntimeSyncIntervalSeconds how often OTK Gateways - should be updated in... - type: integer subSolutionKitNames: description: A list of subSolutionKitNames - all,internal or dmz cover the primary use... items: type: string type: array - syncIntervalSeconds: - description: SyncIntervalSeconds determines how often DMZ - and Internal gateways should... - type: integer type: description: Type of OTK installation single, internal or dmz diff --git a/internal/controller/gateway/controller.go b/internal/controller/gateway/controller.go index af83bb95..92ebd542 100644 --- a/internal/controller/gateway/controller.go +++ b/internal/controller/gateway/controller.go @@ -111,9 +111,9 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } if gw.Spec.App.Otk.Enabled { - if gw.Spec.App.Otk.Type == securityv1.OtkTypeDMZ || gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal { - ops = append(ops, ReconcileOperations{reconcile.ScheduledJobs, "scheduled jobs"}) - } + // if gw.Spec.App.Otk.Type == securityv1.OtkTypeDMZ || gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal { + // ops = append(ops, ReconcileOperations{reconcile.ScheduledJobs, "scheduled jobs"}) + // } if gw.Spec.App.Otk.Type == securityv1.OtkTypeSingle && !gw.Spec.App.Management.Database.Enabled { ops = append(ops, ReconcileOperations{reconcile.OTKDatabaseMaintenanceTasks, "otk-db-maintenance-tasks"}) } @@ -236,11 +236,11 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error { req = append(req, creconcile.Request{NamespacedName: types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}}) } } - if gateway.Spec.App.Otk.Enabled { - if gateway.Spec.App.Otk.DmzKeySecret == a.GetName() || gateway.Spec.App.Otk.InternalKeySecret == a.GetName() { - req = append(req, creconcile.Request{NamespacedName: types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}}) - } - } + // if gateway.Spec.App.Otk.Enabled { + // if gateway.Spec.App.Otk.DmzKeySecret == a.GetName() || gateway.Spec.App.Otk.InternalKeySecret == a.GetName() { + // req = append(req, creconcile.Request{NamespacedName: types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name}}) + // } + // } } return req }), diff --git a/pkg/gateway/reconcile/cron.go b/pkg/gateway/reconcile/cron.go deleted file mode 100644 index 2de5da1c..00000000 --- a/pkg/gateway/reconcile/cron.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -* Copyright (c) 2025 Broadcom. All rights reserved. -* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. -* All trademarks, trade names, service marks, and logos referenced -* herein belong to their respective companies. -* -* This software and all information contained therein is confidential -* and proprietary and shall not be duplicated, used, disclosed or -* disseminated in any way except as authorized by the applicable -* license agreement, without the express written permission of Broadcom. -* All authorized reproductions must be marked with this language. -* -* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE -* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS -* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION -* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, -* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO -* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR -* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, -* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR -* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE -* POSSIBILITY OF SUCH LOSS OR DAMAGE. -* - */ -package reconcile - -import ( - "context" - "time" - - securityv1 "github.com/caapim/layer7-operator/api/v1" - "github.com/caapim/layer7-operator/pkg/util" - "github.com/go-co-op/gocron" -) - -var s = gocron.NewScheduler(time.Local) -var syncCache = util.NewSyncCache(3 * time.Second) - -func ScheduledJobs(ctx context.Context, params Params) error { - - registerJobs(ctx, params) - - for _, j := range s.Jobs() { - for _, t := range j.Tags() { - if !j.IsRunning() { - params.Log.V(2).Info("starting job", "job", t, "namespace", params.Instance.Namespace) - err := s.RunByTag(t) - if err != nil { - params.Log.V(2).Info("no job with given tag", "job", t, "namespace", params.Instance.Namespace) - } - } - } - } - if !s.IsRunning() { - s.StartAsync() - } - return nil -} - -func registerJobs(ctx context.Context, params Params) { - s.TagsUnique() - otkSyncInterval := 10 - - if params.Instance.Spec.App.Otk.RuntimeSyncIntervalSeconds != 0 { - otkSyncInterval = params.Instance.Spec.App.Otk.RuntimeSyncIntervalSeconds - } - - if params.Instance.Spec.App.Otk.Enabled && (params.Instance.Spec.App.Otk.Type == securityv1.OtkTypeDMZ || params.Instance.Spec.App.Otk.Type == securityv1.OtkTypeInternal) { - _, err := s.Every(otkSyncInterval).Seconds().Tag(params.Instance.Name+"-sync-otk-policies").Do(syncOtkPolicies, ctx, params) - - if err != nil { - params.Log.V(2).Info("otk policy sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } - - // Register certificate sync job for DMZ and Internal gateways - // Use SyncIntervalSeconds if specified, otherwise fall back to RuntimeSyncIntervalSeconds or default - certSyncInterval := otkSyncInterval - if params.Instance.Spec.App.Otk.SyncIntervalSeconds != 0 { - certSyncInterval = params.Instance.Spec.App.Otk.SyncIntervalSeconds - } - - _, err = s.Every(certSyncInterval).Seconds().Tag(params.Instance.Name+"-sync-otk-certificates").Do(syncOtkCertificates, ctx, params) - - if err != nil { - params.Log.V(2).Info("otk certificate sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } - - // Register external keys sync job for certificate publishing between DMZ and Internal - _, err = s.Every(certSyncInterval).Seconds().Tag(params.Instance.Name+"-sync-otk-external-keys").Do(syncOtkExternalKeys, ctx, params) - - if err != nil { - params.Log.V(2).Info("otk external keys sync job already registered", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } - } -} - -func syncOtkExternalKeys(ctx context.Context, params Params) { - // Sync certificates between DMZ and Internal gateways via ExternalKeys - // This handles OTK certificate publishing (publishDmzCertToInternal, publishInternalCertToDmz) - err := ExternalKeys(ctx, params) - if err != nil { - params.Log.Error(err, "failed to sync OTK external keys certificates", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - } else { - params.Log.V(2).Info("OTK external keys certificates synced", "name", params.Instance.Name, "namespace", params.Instance.Namespace, "interval", params.Instance.Spec.App.Otk.SyncIntervalSeconds) - } -} - -func removeJob(tag string) error { - err := s.RemoveByTag(tag) - if err != nil { - return err - } - return nil -} diff --git a/pkg/gateway/reconcile/externalkeys.go b/pkg/gateway/reconcile/externalkeys.go index a19e3300..3192ec9d 100644 --- a/pkg/gateway/reconcile/externalkeys.go +++ b/pkg/gateway/reconcile/externalkeys.go @@ -28,35 +28,11 @@ package reconcile import ( "context" - "crypto/sha1" - "crypto/x509" - "encoding/json" - "encoding/pem" - "fmt" - "strings" - - securityv1 "github.com/caapim/layer7-operator/api/v1" - "github.com/caapim/layer7-operator/internal/graphman" - "github.com/caapim/layer7-operator/pkg/util" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) func ExternalKeys(ctx context.Context, params Params) error { gateway := params.Instance - // Handle OTK keys if OTK is enabled - if gateway.Spec.App.Otk.Enabled { - err := handleOtkKeys(ctx, params, gateway) - if err != nil { - params.Log.Error(err, "failed to handle OTK keys", "name", gateway.Name, "namespace", gateway.Namespace) - return err - } - } - - // Handle regular external keys if len(gateway.Spec.App.ExternalKeys) == 0 && len(gateway.Status.LastAppliedExternalKeys) == 0 { return nil } @@ -91,512 +67,3 @@ func ExternalKeys(ctx context.Context, params Params) error { return nil } - -func handleOtkKeys(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - // Handle DMZ key updates only if there's an externalKey with otk: true referencing the same secret - if gateway.Spec.App.Otk.DmzKeySecret != "" && gateway.Spec.App.Otk.Type == securityv1.OtkTypeDMZ { - // Check if there's an externalKey with otk: true that references this secret - hasOtkExternalKey := false - for _, ek := range gateway.Spec.App.ExternalKeys { - if ek.Enabled && ek.Otk && ek.Name == gateway.Spec.App.Otk.DmzKeySecret { - hasOtkExternalKey = true - break - } - } - - // Only process if there's an externalKey with otk: true - if hasOtkExternalKey { - err := handleDmzKeyUpdate(ctx, params, gateway) - if err != nil { - params.Log.Error(err, "failed to handle DMZ key update", "name", gateway.Name, "namespace", gateway.Namespace) - return err - } - } else { - params.Log.V(2).Info("Skipping DMZ key update - no externalKey with otk: true found", "secret", gateway.Spec.App.Otk.DmzKeySecret) - } - } - - // Handle Internal key updates only if there's an externalKey with otk: true referencing the same secret - if gateway.Spec.App.Otk.InternalKeySecret != "" && gateway.Spec.App.Otk.Type == securityv1.OtkTypeInternal { - // Check if there's an externalKey with otk: true that references this secret - hasOtkExternalKey := false - for _, ek := range gateway.Spec.App.ExternalKeys { - if ek.Enabled && ek.Otk && ek.Name == gateway.Spec.App.Otk.InternalKeySecret { - hasOtkExternalKey = true - break - } - } - - // Only process if there's an externalKey with otk: true - if hasOtkExternalKey { - err := handleInternalKeyUpdate(ctx, params, gateway) - if err != nil { - params.Log.Error(err, "failed to handle Internal key update", "name", gateway.Name, "namespace", gateway.Namespace) - return err - } - } else { - params.Log.V(2).Info("Skipping Internal key update - no externalKey with otk: true found", "secret", gateway.Spec.App.Otk.InternalKeySecret) - } - } - - return nil -} - -func handleDmzKeyUpdate(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - // Get DMZ key secret - dmzKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzKeySecret) - if err != nil { - if k8serrors.IsNotFound(err) { - params.Log.V(2).Info("DMZ key secret not found, skipping", "secret", gateway.Spec.App.Otk.DmzKeySecret) - return nil - } - return err - } - - // Check if operator managed (ephemeral mode) - isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled - - if isOperatorManaged { - // Update DMZ with the new key (key sync only, cert publishing handled by syncOtkCertificates) - err = updateDmzWithKey(ctx, params, gateway, dmzKeySecret) - if err != nil { - return fmt.Errorf("failed to update DMZ with key: %w", err) - } - } - - return nil -} - -func handleInternalKeyUpdate(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - // Get Internal key secret - internalKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalKeySecret) - if err != nil { - if k8serrors.IsNotFound(err) { - params.Log.V(2).Info("Internal key secret not found, skipping", "secret", gateway.Spec.App.Otk.InternalKeySecret) - return nil - } - return err - } - - // Check if operator managed (ephemeral mode) - isOperatorManaged := !gateway.Spec.App.Management.Database.Enabled - - if isOperatorManaged { - // Update Internal with the new key (key sync only, cert publishing handled by syncOtkCertificates) - err = updateInternalWithKey(ctx, params, gateway, internalKeySecret) - if err != nil { - return fmt.Errorf("failed to update Internal with key: %w", err) - } - } - - return nil -} - -func updateDmzWithKey(ctx context.Context, params Params, gateway *securityv1.Gateway, keySecret *corev1.Secret) error { - if keySecret.Type != corev1.SecretTypeTLS { - return fmt.Errorf("DMZ key secret must be of type kubernetes.io/tls") - } - - certData := keySecret.Data["tls.crt"] - keyData := keySecret.Data["tls.key"] - - if len(certData) == 0 || len(keyData) == 0 { - return fmt.Errorf("DMZ key secret must contain tls.crt and tls.key") - } - - // Extract certificate from chain - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format in DMZ key secret") - } - - // Use first certificate in chain - firstCert := crtStrings[0] - b, _ := pem.Decode([]byte(firstCert)) - if b == nil { - return fmt.Errorf("failed to decode certificate") - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - return fmt.Errorf("failed to parse certificate: %w", err) - } - - // Create Graphman key bundle - keySecretMap := []util.GraphmanKey{ - { - Name: crtX509.Subject.CommonName, - Crt: string(certData), - Key: string(keyData), - Alias: "otk-dmz-key", - UsageType: "", - }, - } - - bundleBytes, err := util.ConvertX509ToGraphmanBundle(keySecretMap, []string{}) - if err != nil { - return fmt.Errorf("failed to convert key to bundle: %w", err) - } - - // Calculate checksum - dataBytes, _ := json.Marshal(&keySecretMap) - h := sha1.New() - h.Write(dataBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Get gateway secret for authentication - name := gateway.Name - if gateway.Spec.App.Management.SecretName != "" { - name = gateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - if err != nil { - return err - } - - annotation := "security.brcmlabs.com/otk-dmz-key" - - // Check if DMZ key was already applied (to determine if update is needed) - keyWasUpdated := false - currentSha1Sum := "" - if !gateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, params) - if err != nil { - return err - } - // Check current annotation value before update - for _, pod := range podList.Items { - if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { - currentSha1Sum = val - break - } - } - err = ReconcileEphemeralGateway(ctx, params, "otk dmz key", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) - if err != nil { - return err - } - // Key was updated if sha1Sum changed - keyWasUpdated = currentSha1Sum != sha1Sum - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err != nil { - return err - } - // Check current annotation value before update - currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] - err = ReconcileDBGateway(ctx, params, "otk dmz key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk dmz key", bundleBytes) - if err != nil { - return err - } - // Key was updated if sha1Sum changed (ReconcileDBGateway returns early if already applied) - keyWasUpdated = currentSha1Sum != sha1Sum - } - - // Update cluster property if DMZ key was updated OR if this is the first reconciliation (currentSha1Sum is empty) - // This ensures the CWP is set on first reconciliation, not just on key updates - if keyWasUpdated || currentSha1Sum == "" { - if err := updateDmzPrivateKeyClusterProperty(ctx, params, gateway, "otk-dmz-key"); err != nil { - params.Log.V(2).Info("Failed to update DMZ private key cluster property", "error", err, "gateway", gateway.Name) - // Don't fail the entire operation if cluster property update fails - } - } else { - params.Log.V(2).Info("DMZ key was not updated, skipping cluster property update", "gateway", gateway.Name) - } - - return nil -} - -func updateInternalWithKey(ctx context.Context, params Params, gateway *securityv1.Gateway, keySecret *corev1.Secret) error { - if keySecret.Type != corev1.SecretTypeTLS { - return fmt.Errorf("Internal key secret must be of type kubernetes.io/tls") - } - - certData := keySecret.Data["tls.crt"] - keyData := keySecret.Data["tls.key"] - - if len(certData) == 0 || len(keyData) == 0 { - return fmt.Errorf("Internal key secret must contain tls.crt and tls.key") - } - - // Extract certificate from chain - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format in Internal key secret") - } - - // Use first certificate in chain - firstCert := crtStrings[0] - b, _ := pem.Decode([]byte(firstCert)) - if b == nil { - return fmt.Errorf("failed to decode certificate") - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - return fmt.Errorf("failed to parse certificate: %w", err) - } - - // Create Graphman key bundle - keySecretMap := []util.GraphmanKey{ - { - Name: crtX509.Subject.CommonName, - Crt: string(certData), - Key: string(keyData), - Alias: "otk-internal-key", - UsageType: "", - }, - } - - bundleBytes, err := util.ConvertX509ToGraphmanBundle(keySecretMap, []string{}) - if err != nil { - return fmt.Errorf("failed to convert key to bundle: %w", err) - } - - // Calculate checksum - dataBytes, _ := json.Marshal(&keySecretMap) - h := sha1.New() - h.Write(dataBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Get gateway secret for authentication - name := gateway.Name - if gateway.Spec.App.Management.SecretName != "" { - name = gateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - if err != nil { - return err - } - - annotation := "security.brcmlabs.com/otk-internal-key" - - if !gateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, params) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, params, "otk internal key", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk internal key", bundleBytes) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, params, "otk internal key", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk internal key", bundleBytes) - if err != nil { - return err - } - } - - return nil -} - -// checkClusterPropertyExists checks if the cluster property exists in the ConfigMap -func checkClusterPropertyExists(ctx context.Context, params Params, gateway *securityv1.Gateway, propertyName string) bool { - // Only check for DMZ gateway type - if gateway.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { - return false - } - - // Get the cluster properties ConfigMap - cmName := gateway.Name + "-cwp-bundle" - cm, err := getGatewayConfigMap(ctx, params, cmName) - if err != nil { - return false - } - - // Parse existing bundle - bundleJSON := cm.Data["cwp.json"] - if bundleJSON == "" { - return false - } - - bundle := graphman.Bundle{} - err = json.Unmarshal([]byte(bundleJSON), &bundle) - if err != nil { - return false - } - - // Check if property exists - for _, cwp := range bundle.ClusterProperties { - if cwp.Name == propertyName { - return true - } - } - - return false -} - -// updateDmzPrivateKeyClusterProperty updates the cluster property otk.dmz.private_key.name -// with the DMZ private key name. This function only updates the property if it exists. -// It does not create the property if it doesn't exist. -func updateDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gateway *securityv1.Gateway, keyName string) error { - // Only update cluster property for DMZ gateway type - if gateway.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { - return nil - } - - // Get the cluster properties ConfigMap - cmName := gateway.Name + "-cwp-bundle" - cm, err := getGatewayConfigMap(ctx, params, cmName) - if err != nil { - if !k8serrors.IsNotFound(err) { - return fmt.Errorf("failed to get cluster properties ConfigMap: %w", err) - } - // ConfigMap doesn't exist, property doesn't exist - skip update - return fmt.Errorf("cluster property ConfigMap does not exist") - } - - // Parse existing bundle - bundle := graphman.Bundle{} - bundleJSON := cm.Data["cwp.json"] - if bundleJSON == "" { - // Empty bundle, property doesn't exist - skip update - return fmt.Errorf("cluster property bundle is empty") - } - - err = json.Unmarshal([]byte(bundleJSON), &bundle) - if err != nil { - return fmt.Errorf("failed to parse cluster properties bundle: %w", err) - } - - // Initialize bundle properties if nil - if bundle.Properties == nil { - bundle.Properties = &graphman.BundleProperties{ - Mappings: graphman.BundleMappings{}, - } - } - - // Check if property exists and update it - propertyName := "otk.dmz.private_key.name" - found := false - for _, cwp := range bundle.ClusterProperties { - if cwp.Name == propertyName { - cwp.Value = keyName - found = true - break - } - } - - if !found { - // Property doesn't exist - skip update (only update, don't create) - return fmt.Errorf("cluster property %s does not exist", propertyName) - } - - // Marshal bundle back to JSON - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return fmt.Errorf("failed to marshal cluster properties bundle: %w", err) - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Update ConfigMap - cm.Data["cwp.json"] = string(bundleBytes) - if cm.ObjectMeta.Annotations == nil { - cm.ObjectMeta.Annotations = make(map[string]string) - } - cm.ObjectMeta.Annotations["checksum/data"] = sha1Sum - - err = params.Client.Update(ctx, cm) - if err != nil { - return fmt.Errorf("failed to update cluster properties ConfigMap: %w", err) - } - - params.Log.V(2).Info("Updated cluster property ConfigMap", "property", propertyName, "value", keyName, "gateway", gateway.Name) - - // Apply the cluster property using the existing mechanism - gwUpdReq, err := NewGwUpdateRequest( - ctx, - gateway, - params, - WithBundleType(BundleTypeClusterProp), - ) - if err != nil { - return fmt.Errorf("failed to create gateway update request: %w", err) - } - - err = SyncGateway(ctx, params, *gwUpdReq) - if err != nil { - return fmt.Errorf("failed to sync cluster property: %w", err) - } - - params.Log.V(2).Info("Applied cluster property", "property", propertyName, "value", keyName, "gateway", gateway.Name) - - return nil -} - -// createDmzPrivateKeyClusterProperty creates a new cluster properties ConfigMap with the DMZ private key property -func createDmzPrivateKeyClusterProperty(ctx context.Context, params Params, gateway *securityv1.Gateway, keyName string, cmName string) error { - // Create new bundle with the property - bundle := graphman.Bundle{ - ClusterProperties: []*graphman.ClusterPropertyInput{ - { - Name: "otk.dmz.private_key.name", - Value: keyName, - }, - }, - Properties: &graphman.BundleProperties{ - Mappings: graphman.BundleMappings{}, - }, - } - - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return fmt.Errorf("failed to marshal cluster properties bundle: %w", err) - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Create ConfigMap - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmName, - Namespace: gateway.Namespace, - Annotations: map[string]string{ - "checksum/data": sha1Sum, - }, - }, - Data: map[string]string{ - "cwp.json": string(bundleBytes), - }, - } - - // Set controller reference - if err := controllerutil.SetControllerReference(gateway, cm, params.Scheme); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - err = params.Client.Create(ctx, cm) - if err != nil { - return fmt.Errorf("failed to create cluster properties ConfigMap: %w", err) - } - - params.Log.V(2).Info("Created cluster property ConfigMap", "property", "otk.dmz.private_key.name", "value", keyName, "gateway", gateway.Name) - - // Apply the cluster property using the existing mechanism - gwUpdReq, err := NewGwUpdateRequest( - ctx, - gateway, - params, - WithBundleType(BundleTypeClusterProp), - ) - if err != nil { - return fmt.Errorf("failed to create gateway update request: %w", err) - } - - err = SyncGateway(ctx, params, *gwUpdReq) - if err != nil { - return fmt.Errorf("failed to sync cluster property: %w", err) - } - - params.Log.V(2).Info("Applied cluster property", "property", "otk.dmz.private_key.name", "value", keyName, "gateway", gateway.Name) - - return nil -} diff --git a/pkg/gateway/reconcile/gateway.go b/pkg/gateway/reconcile/gateway.go index f2ca4434..0dc931fe 100644 --- a/pkg/gateway/reconcile/gateway.go +++ b/pkg/gateway/reconcile/gateway.go @@ -96,6 +96,7 @@ const ( BundleTypeClusterProp BundleType = "cluster properties" BundleTypeListenPort BundleType = "listen ports" BundleTypeOTKDatabaseMaintenance BundleType = "otk db maintenance" + BundleTypeOTKFips BundleType = "otk fips" ) type GatewayUpdateRequestOpt func(*GatewayUpdateRequest) @@ -107,6 +108,8 @@ type MappingSource struct { ThumbprintSha1 string `json:"thumbprintSha1,omitempty"` } +var syncCache = util.NewSyncCache(3 * time.Second) + func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params Params, opts ...GatewayUpdateRequestOpt) (*GatewayUpdateRequest, error) { graphmanPort := 9443 if gateway.Spec.App.Management.Graphman.DynamicSyncPort != 0 { @@ -499,7 +502,7 @@ func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params for _, k := range gateway.Status.LastAppliedExternalKeys { found := false for _, ek := range gateway.Spec.App.ExternalKeys { - if k == ek.Alias && ek.Enabled && !ek.Otk { + if k == ek.Alias && ek.Enabled { // Only process non-OTK keys in regular external keys flow found = true } @@ -512,7 +515,7 @@ func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params var sha1Sum string for _, externalKey := range gateway.Spec.App.ExternalKeys { - if externalKey.Enabled && !externalKey.Otk { + if externalKey.Enabled { // Skip keys with otk: true - they are handled separately by OTK reconciliation secret, err := getGatewaySecret(ctx, params, externalKey.Name) if err != nil { diff --git a/pkg/gateway/reconcile/l7otkcertificates.go b/pkg/gateway/reconcile/l7otkcertificates.go deleted file mode 100644 index 04de4470..00000000 --- a/pkg/gateway/reconcile/l7otkcertificates.go +++ /dev/null @@ -1,950 +0,0 @@ -/* -* Copyright (c) 2025 Broadcom. All rights reserved. -* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. -* All trademarks, trade names, service marks, and logos referenced -* herein belong to their respective companies. -* -* This software and all information contained therein is confidential -* and proprietary and shall not be duplicated, used, disclosed or -* disseminated in any way except as authorized by the applicable -* license agreement, without the express written permission of Broadcom. -* All authorized reproductions must be marked with this language. -* -* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE -* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS -* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION -* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, -* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO -* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR -* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, -* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR -* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE -* POSSIBILITY OF SUCH LOSS OR DAMAGE. -* - */ -package reconcile - -import ( - "bytes" - "context" - "crypto/sha1" - "crypto/x509" - "encoding/base64" - "encoding/hex" - "encoding/json" - "encoding/pem" - "fmt" - "strings" - - securityv1 "github.com/caapim/layer7-operator/api/v1" - "github.com/caapim/layer7-operator/internal/graphman" - "github.com/caapim/layer7-operator/pkg/util" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" -) - -func syncOtkCertificates(ctx context.Context, params Params) { - gateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{Name: params.Instance.Name, Namespace: params.Instance.Namespace}, gateway) - if err != nil && k8serrors.IsNotFound(err) { - params.Log.Error(err, "gateway not found", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - _ = removeJob(params.Instance.Name + "-" + params.Instance.Namespace + "-sync-otk-certificates") - return - } - - if !gateway.Spec.App.Otk.Enabled { - _ = removeJob(params.Instance.Name + "-" + params.Instance.Namespace + "-sync-otk-certificates") - return - } - - // Publish DMZ certs to Internal Gateway when DMZ key is updated - if gateway.Spec.App.Otk.Type == securityv1.OtkTypeDMZ && gateway.Spec.App.Otk.DmzKeySecret != "" { - err = publishDmzCertificatesToInternal(ctx, params, gateway) - if err != nil { - params.Log.V(2).Info("failed to publish DMZ certificates to Internal", "name", gateway.Name, "namespace", gateway.Namespace, "error", err.Error()) - } - } - - // Publish Internal certs to DMZ Gateway when Internal key is updated - if gateway.Spec.App.Otk.Type == securityv1.OtkTypeInternal && gateway.Spec.App.Otk.InternalKeySecret != "" { - err = publishInternalCertificatesToDmz(ctx, params, gateway) - if err != nil { - params.Log.V(2).Info("failed to publish Internal certificates to DMZ", "name", gateway.Name, "namespace", gateway.Namespace, "error", err.Error()) - } - } -} - -// publishDmzCertificatesToInternal publishes DMZ certificates to Internal gateway when DMZ key is updated -// Handles ephemeral, DB-backed, and external gateways -func publishDmzCertificatesToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - // Check if Internal gateway reference is specified - if gateway.Spec.App.Otk.InternalOtkGatewayReference == "" { - return nil - } - - // Get DMZ key secret - dmzKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzKeySecret) - if err != nil { - if k8serrors.IsNotFound(err) { - params.Log.V(2).Info("DMZ key secret not found, skipping cert publish", "secret", gateway.Spec.App.Otk.DmzKeySecret) - return nil - } - return err - } - - // Check if key was updated by comparing annotation - annotation := "security.brcmlabs.com/otk-dmz-key" - currentSha1Sum := "" - - if !gateway.Spec.App.Management.Database.Enabled { - // Ephemeral gateway - check pod annotations - podList, err := getGatewayPods(ctx, params) - if err != nil { - return err - } - for _, pod := range podList.Items { - if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { - currentSha1Sum = val - break - } - } - } else { - // DB-backed gateway - check deployment annotations - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err != nil { - return err - } - currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] - } - - // Calculate current key checksum - certData := dmzKeySecret.Data["tls.crt"] - keyData := dmzKeySecret.Data["tls.key"] - if len(certData) == 0 || len(keyData) == 0 { - return fmt.Errorf("DMZ key secret must contain tls.crt and tls.key") - } - - keySecretMap := []struct { - Name string - Crt string - Key string - Alias string - UsageType string - }{ - { - Name: "dmz-key", - Crt: string(certData), - Key: string(keyData), - Alias: "otk-dmz-key", - UsageType: "", - }, - } - - dataBytes, _ := json.Marshal(&keySecretMap) - h := sha1.New() - h.Write(dataBytes) - newSha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Only publish if key was updated - if currentSha1Sum == newSha1Sum { - params.Log.V(2).Info("DMZ key not updated, skipping cert publish", "gateway", gateway.Name) - return nil - } - - // Publish DMZ cert to Internal (handles ephemeral, DB-backed, and external gateways) - return publishDmzCertToInternal(ctx, params, gateway, dmzKeySecret) -} - -// publishInternalCertificatesToDmz publishes Internal certificates to DMZ gateway when Internal key is updated -// Handles ephemeral, DB-backed, and external gateways -func publishInternalCertificatesToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - // Check if DMZ gateway reference is specified - if gateway.Spec.App.Otk.DmzOtkGatewayReference == "" { - return nil - } - - // Get Internal key secret - internalKeySecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalKeySecret) - if err != nil { - if k8serrors.IsNotFound(err) { - params.Log.V(2).Info("Internal key secret not found, skipping cert publish", "secret", gateway.Spec.App.Otk.InternalKeySecret) - return nil - } - return err - } - - // Check if key was updated by comparing annotation - annotation := "security.brcmlabs.com/otk-internal-key" - currentSha1Sum := "" - - if !gateway.Spec.App.Management.Database.Enabled { - // Ephemeral gateway - check pod annotations - podList, err := getGatewayPods(ctx, params) - if err != nil { - return err - } - for _, pod := range podList.Items { - if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { - currentSha1Sum = val - break - } - } - } else { - // DB-backed gateway - check deployment annotations - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err != nil { - return err - } - currentSha1Sum = gatewayDeployment.ObjectMeta.Annotations[annotation] - } - - // Calculate current key checksum - certData := internalKeySecret.Data["tls.crt"] - keyData := internalKeySecret.Data["tls.key"] - if len(certData) == 0 || len(keyData) == 0 { - return fmt.Errorf("Internal key secret must contain tls.crt and tls.key") - } - - keySecretMap := []struct { - Name string - Crt string - Key string - Alias string - UsageType string - }{ - { - Name: "internal-key", - Crt: string(certData), - Key: string(keyData), - Alias: "otk-internal-key", - UsageType: "", - }, - } - - dataBytes, _ := json.Marshal(&keySecretMap) - h := sha1.New() - h.Write(dataBytes) - newSha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // Only publish if key was updated - if currentSha1Sum == newSha1Sum { - params.Log.V(2).Info("Internal key not updated, skipping cert publish", "gateway", gateway.Name) - return nil - } - - // Publish Internal cert to DMZ (handles ephemeral, DB-backed, and external gateways) - return publishInternalCertToDmz(ctx, params, gateway, internalKeySecret) -} - -func publishDmzCertToInternal(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret) error { - // Get Internal gateway - internalGateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{ - Name: gateway.Spec.App.Otk.InternalOtkGatewayReference, - Namespace: gateway.Namespace, - }, internalGateway) - - isExternalGateway := false - if err != nil { - if k8serrors.IsNotFound(err) { - // Gateway not found - check if it's external (port specified) - if gateway.Spec.App.Otk.InternalGatewayPort != 0 { - params.Log.V(2).Info("Internal gateway not found but port specified, treating as external", - "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference, - "port", gateway.Spec.App.Otk.InternalGatewayPort) - isExternalGateway = true - } else { - params.Log.V(2).Info("Internal gateway not found and no port specified, skipping cert publish", - "gateway", gateway.Spec.App.Otk.InternalOtkGatewayReference) - return nil - } - } else { - return err - } - } - - certData := dmzKeySecret.Data["tls.crt"] - if len(certData) == 0 { - return fmt.Errorf("DMZ key secret must contain tls.crt") - } - - // Parse certificate - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format") - } - - // Before adding new certs, remove existing ones if they were previously applied - // Check if certs were previously applied by checking the annotation - annotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates" - thumbprintAnnotation := "security.brcmlabs.com/" + gateway.Name + "-dmz-certificates-thumbprints" - previousCertChecksum := "" - var oldThumbprints []string - if !isExternalGateway { - if !internalGateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, params) - if err == nil { - for _, pod := range podList.Items { - if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { - previousCertChecksum = val - } - if val, ok := pod.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { - // Parse comma-separated thumbprints - oldThumbprints = strings.Split(val, ",") - } - if previousCertChecksum != "" { - break - } - } - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err == nil { - previousCertChecksum = gatewayDeployment.ObjectMeta.Annotations[annotation] - if val, ok := gatewayDeployment.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { - oldThumbprints = strings.Split(val, ",") - } - } - } - } - - bundle := graphman.Bundle{} - - // If we have old thumbprints, add deletion mappings before adding new certs - if len(oldThumbprints) > 0 && previousCertChecksum != "" { - if bundle.Properties == nil { - bundle.Properties = &graphman.BundleProperties{ - Mappings: graphman.BundleMappings{}, - } - } - for _, thumbprint := range oldThumbprints { - thumbprint = strings.TrimSpace(thumbprint) - if thumbprint != "" { - bundle.Properties.Mappings.TrustedCerts = append(bundle.Properties.Mappings.TrustedCerts, &graphman.MappingInstructionInput{ - Action: graphman.MappingActionDelete, - Source: graphman.MappingSource{ThumbprintSha1: thumbprint}, - }) - } - } - // Also remove old FIP users with the same names - // We'll identify them by the cert CommonName pattern - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - // Remove FIP user by name (CommonName) - bundle.Properties.Mappings.FipUsers = append(bundle.Properties.Mappings.FipUsers, &graphman.MappingInstructionInput{ - Action: graphman.MappingActionDelete, - Source: graphman.MappingSource{Name: crtX509.Subject.CommonName}, - }) - } - } - - // Calculate thumbprints for new certs and add to TrustedCerts - var newThumbprints []string - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - // Calculate thumbprint for this cert - thumbprint, err := calculateCertThumbprint(crtX509.Raw) - if err != nil { - params.Log.V(2).Info("Failed to calculate cert thumbprint", "error", err, "cert", crtX509.Subject.CommonName) - thumbprint = "" // Continue without thumbprint - } else { - newThumbprints = append(newThumbprints, thumbprint) - } - - bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: crtX509.Subject.CommonName, - CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), - ThumbprintSha1: thumbprint, - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, - }) - - // Add to FIP Users - bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ - Name: crtX509.Subject.CommonName, - ProviderName: "otk-fips-provider", - SubjectDn: "cn=" + crtX509.Subject.CommonName, - CertBase64: base64.RawStdEncoding.EncodeToString(crtX509.Raw), - }) - } - - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return err - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // If gateway is external (not managed by operator), use specified port and auth secret - if isExternalGateway { - // Parse certificates to extract information for FIP user creation - var certInfo []struct { - commonName string - subjectDn string - certRaw []byte - } - - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - // Extract full Subject DN from certificate - subjectDn := extractSubjectDN(crtX509) - - certInfo = append(certInfo, struct { - commonName string - subjectDn string - certRaw []byte - }{ - commonName: crtX509.Subject.CommonName, - subjectDn: subjectDn, - certRaw: crtX509.Raw, - }) - } - - return syncDmzCertToExternalInternalGateway(ctx, params, gateway, dmzKeySecret, certInfo) - } - - // Get Internal gateway secret - name := internalGateway.Name - if internalGateway.Spec.App.Management.SecretName != "" { - name = internalGateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - if err != nil { - return err - } - - internalParams := params - internalParams.Instance = internalGateway - - // Note: InternalGatewayPort is used when the gateway is external (not found in cluster) - // For operator-managed gateways, the gateway's own graphman port configuration is used - - if !internalGateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, internalParams) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, internalParams, "otk certificates", *podList, internalGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, internalParams) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, internalParams, "otk certificates", *gatewayDeployment, internalGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) - if err != nil { - return err - } - } - - return nil -} - -func publishInternalCertToDmz(ctx context.Context, params Params, gateway *securityv1.Gateway, internalKeySecret *corev1.Secret) error { - // Get DMZ gateway - dmzGateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{ - Name: gateway.Spec.App.Otk.DmzOtkGatewayReference, - Namespace: gateway.Namespace, - }, dmzGateway) - - isExternalGateway := false - if err != nil { - if k8serrors.IsNotFound(err) { - // Gateway not found - check if it's external (port specified) - if gateway.Spec.App.Otk.DmzGatewayPort != 0 { - params.Log.V(2).Info("DMZ gateway not found but port specified, treating as external", - "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference, - "port", gateway.Spec.App.Otk.DmzGatewayPort) - isExternalGateway = true - } else { - params.Log.V(2).Info("DMZ gateway not found and no port specified, skipping cert publish", - "gateway", gateway.Spec.App.Otk.DmzOtkGatewayReference) - return nil - } - } else { - return err - } - } - - certData := internalKeySecret.Data["tls.crt"] - if len(certData) == 0 { - return fmt.Errorf("Internal key secret must contain tls.crt") - } - - // Parse certificate - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - if len(crtStrings) == 0 { - return fmt.Errorf("invalid certificate format") - } - - // Before adding new certs, remove existing ones if they were previously applied - // Check if certs were previously applied by checking the annotation - annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" - thumbprintAnnotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates-thumbprints" - previousCertChecksum := "" - var oldThumbprints []string - if !isExternalGateway { - if !dmzGateway.Spec.App.Management.Database.Enabled { - dmzParams := params - dmzParams.Instance = dmzGateway - podList, err := getGatewayPods(ctx, dmzParams) - if err == nil { - for _, pod := range podList.Items { - if val, ok := pod.ObjectMeta.Annotations[annotation]; ok { - previousCertChecksum = val - } - if val, ok := pod.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { - // Parse comma-separated thumbprints - oldThumbprints = strings.Split(val, ",") - } - if previousCertChecksum != "" { - break - } - } - } - } else { - dmzParams := params - dmzParams.Instance = dmzGateway - gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) - if err == nil { - previousCertChecksum = gatewayDeployment.ObjectMeta.Annotations[annotation] - if val, ok := gatewayDeployment.ObjectMeta.Annotations[thumbprintAnnotation]; ok && val != "" { - oldThumbprints = strings.Split(val, ",") - } - } - } - } - - bundle := graphman.Bundle{} - - // If we have old thumbprints, add deletion mappings before adding new certs - if len(oldThumbprints) > 0 && previousCertChecksum != "" { - if bundle.Properties == nil { - bundle.Properties = &graphman.BundleProperties{ - Mappings: graphman.BundleMappings{}, - } - } - for _, thumbprint := range oldThumbprints { - thumbprint = strings.TrimSpace(thumbprint) - if thumbprint != "" { - bundle.Properties.Mappings.TrustedCerts = append(bundle.Properties.Mappings.TrustedCerts, &graphman.MappingInstructionInput{ - Action: graphman.MappingActionDelete, - Source: graphman.MappingSource{ThumbprintSha1: thumbprint}, - }) - } - } - } - - // Calculate thumbprints for new certs and add to TrustedCerts - var newThumbprints []string - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - // Calculate thumbprint for this cert - thumbprint, err := calculateCertThumbprint(crtX509.Raw) - if err != nil { - params.Log.V(2).Info("Failed to calculate cert thumbprint", "error", err, "cert", crtX509.Subject.CommonName) - thumbprint = "" // Continue without thumbprint - } else { - newThumbprints = append(newThumbprints, thumbprint) - } - - bundle.TrustedCerts = append(bundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: crtX509.Subject.CommonName, - CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), - ThumbprintSha1: thumbprint, - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, - }) - } - - bundleBytes, err := json.Marshal(bundle) - if err != nil { - return err - } - - // Calculate checksum - h := sha1.New() - h.Write(bundleBytes) - sha1Sum := fmt.Sprintf("%x", h.Sum(nil)) - - // If gateway is external (not managed by operator), use specified port and auth secret - if isExternalGateway { - return syncInternalCertToExternalDmzGateway(ctx, params, gateway, bundleBytes, sha1Sum) - } - - // Get DMZ gateway secret - name := dmzGateway.Name - if dmzGateway.Spec.App.Management.SecretName != "" { - name = dmzGateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - if err != nil { - return err - } - - // annotation is already declared above, reuse it - // annotation := "security.brcmlabs.com/" + gateway.Name + "-internal-certificates" - - dmzParams := params - dmzParams.Instance = dmzGateway - - // Note: DmzGatewayPort is used when the gateway is external (not found in cluster) - // For operator-managed gateways, the gateway's own graphman port configuration is used - - if !dmzGateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, dmzParams) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, dmzParams, "otk certificates", *podList, dmzGateway, gwSecret, "", annotation, sha1Sum, true, "otk certificates", bundleBytes) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, dmzParams) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, dmzParams, "otk certificates", *gatewayDeployment, dmzGateway, gwSecret, "", annotation, sha1Sum, false, "otk certificates", bundleBytes) - if err != nil { - return err - } - } - - return nil -} - -// syncDmzCertToExternalInternalGateway syncs DMZ certificate to an external Internal gateway -// using graphman. First it adds the certificate as a trusted cert, then creates a FIP user. -func syncDmzCertToExternalInternalGateway(ctx context.Context, params Params, gateway *securityv1.Gateway, dmzKeySecret *corev1.Secret, certInfo []struct { - commonName string - subjectDn string - certRaw []byte -}) error { - // Get auth secret for external Internal gateway - if gateway.Spec.App.Otk.InternalAuthSecret == "" { - return fmt.Errorf("internalAuthSecret is required for external Internal gateway") - } - - authSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.InternalAuthSecret) - if err != nil { - return fmt.Errorf("failed to get auth secret for external Internal gateway: %w", err) - } - - // Parse username and password from auth secret - username, password := parseGatewaySecret(authSecret) - if username == "" || password == "" { - return fmt.Errorf("could not retrieve gateway credentials from auth secret: %s", gateway.Spec.App.Otk.InternalAuthSecret) - } - - // Build endpoint URL for external gateway - // Format: :/graphman - // ApplyGraphmanBundle expects format: host:port/path (without https://) - gatewayReference := gateway.Spec.App.Otk.InternalOtkGatewayReference - port := gateway.Spec.App.Otk.InternalGatewayPort - if port == 0 { - port = 9443 // Default graphman port - } - - // For external gateways, the reference might be a hostname or IP - // If it's just a name without domain, we might need to construct a full hostname - // For now, use the reference as-is (could be FQDN, hostname, or IP) - endpoint := fmt.Sprintf("%s:%d/graphman", gatewayReference, port) - - // Step 1: Sync DMZ certificate as TrustedCert first - certData := dmzKeySecret.Data["tls.crt"] - if len(certData) == 0 { - return fmt.Errorf("DMZ key secret must contain tls.crt") - } - - crtStrings := strings.SplitAfter(string(certData), "-----END CERTIFICATE-----") - trustedCertBundle := graphman.Bundle{} - - for _, certStr := range crtStrings { - if certStr == "" { - continue - } - b, _ := pem.Decode([]byte(certStr)) - if b == nil { - continue - } - crtX509, err := x509.ParseCertificate(b.Bytes) - if err != nil { - continue - } - - trustedCertBundle.TrustedCerts = append(trustedCertBundle.TrustedCerts, &graphman.TrustedCertInput{ - Name: crtX509.Subject.CommonName, - CertBase64: base64.StdEncoding.EncodeToString([]byte(certStr)), - TrustAnchor: true, - VerifyHostname: false, - RevocationCheckPolicyType: "USE_DEFAULT", - TrustedFor: []graphman.TrustedForType{ - "SSL", - "SIGNING_SERVER_CERTS", - }, - }) - } - - trustedCertBundleBytes, err := json.Marshal(trustedCertBundle) - if err != nil { - return fmt.Errorf("failed to marshal trusted cert bundle: %w", err) - } - - params.Log.V(2).Info("Syncing DMZ certificate as TrustedCert to external Internal gateway", - "gateway", gatewayReference, - "endpoint", endpoint) - - // Apply trusted cert bundle first - err = util.ApplyGraphmanBundle(username, password, endpoint, "", trustedCertBundleBytes) - if err != nil { - return fmt.Errorf("failed to sync DMZ certificate as TrustedCert to external Internal gateway: %w", err) - } - - params.Log.Info("Successfully synced DMZ certificate as TrustedCert to external Internal gateway", - "gateway", gatewayReference, - "endpoint", endpoint) - - // Step 2: Create FIP user with DMZ certificate - if len(certInfo) == 0 { - params.Log.V(2).Info("No certificate info available, skipping FIP user creation") - return nil - } - - fipUserBundle := graphman.Bundle{} - - for _, info := range certInfo { - // Use the extracted Subject DN (not just "cn=" + CommonName) - // Since FIP identity provider doesn't have a default subject dn, we must provide it - fipUserBundle.FipUsers = append(fipUserBundle.FipUsers, &graphman.FipUserInput{ - Name: info.commonName, - ProviderName: "otk-fips-provider", - SubjectDn: info.subjectDn, // Full Subject DN from certificate - CertBase64: base64.RawStdEncoding.EncodeToString(info.certRaw), - }) - } - - fipUserBundleBytes, err := json.Marshal(fipUserBundle) - if err != nil { - return fmt.Errorf("failed to marshal FIP user bundle: %w", err) - } - - params.Log.V(2).Info("Creating FIP user with DMZ certificate in external Internal gateway", - "gateway", gatewayReference, - "endpoint", endpoint) - - // Apply FIP user bundle after certificate is synced - err = util.ApplyGraphmanBundle(username, password, endpoint, "", fipUserBundleBytes) - if err != nil { - return fmt.Errorf("failed to create FIP user with DMZ certificate in external Internal gateway: %w", err) - } - - params.Log.Info("Successfully created FIP user with DMZ certificate in external Internal gateway", - "gateway", gatewayReference, - "endpoint", endpoint) - - return nil -} - -// extractSubjectDN extracts the full Subject DN from an x509 certificate -// Format: CN=name,OU=org unit,O=org,C=country, etc. -func extractSubjectDN(cert *x509.Certificate) string { - var parts []string - - // Add CommonName - if cert.Subject.CommonName != "" { - parts = append(parts, "CN="+cert.Subject.CommonName) - } - - // Add Country - for _, c := range cert.Subject.Country { - if c != "" { - parts = append(parts, "C="+c) - } - } - - // Add Organization - for _, o := range cert.Subject.Organization { - if o != "" { - parts = append(parts, "O="+o) - } - } - - // Add Organizational Unit - for _, ou := range cert.Subject.OrganizationalUnit { - if ou != "" { - parts = append(parts, "OU="+ou) - } - } - - // Add Locality - for _, l := range cert.Subject.Locality { - if l != "" { - parts = append(parts, "L="+l) - } - } - - // Add Province/State - for _, p := range cert.Subject.Province { - if p != "" { - parts = append(parts, "ST="+p) - } - } - - // Add Street Address - for _, s := range cert.Subject.StreetAddress { - if s != "" { - parts = append(parts, "STREET="+s) - } - } - - // Add Postal Code - for _, pc := range cert.Subject.PostalCode { - if pc != "" { - parts = append(parts, "POSTALCODE="+pc) - } - } - - // Add Serial Number - if cert.Subject.SerialNumber != "" { - parts = append(parts, "SERIALNUMBER="+cert.Subject.SerialNumber) - } - - // Join all parts with comma - if len(parts) == 0 { - // Fallback to CN if nothing else is available - if cert.Subject.CommonName != "" { - return "CN=" + cert.Subject.CommonName - } - return "" - } - - return strings.Join(parts, ",") -} - -// syncInternalCertToExternalDmzGateway syncs Internal certificate to an external DMZ gateway -// using graphman. It adds the certificate as a trusted cert. -func syncInternalCertToExternalDmzGateway(ctx context.Context, params Params, gateway *securityv1.Gateway, bundleBytes []byte, sha1Sum string) error { - // Get auth secret for external DMZ gateway - if gateway.Spec.App.Otk.DmzAuthSecret == "" { - return fmt.Errorf("dmzAuthSecret is required for external DMZ gateway") - } - - authSecret, err := getGatewaySecret(ctx, params, gateway.Spec.App.Otk.DmzAuthSecret) - if err != nil { - return fmt.Errorf("failed to get auth secret for external DMZ gateway: %w", err) - } - - // Parse username and password from auth secret - username, password := parseGatewaySecret(authSecret) - if username == "" || password == "" { - return fmt.Errorf("could not retrieve gateway credentials from auth secret: %s", gateway.Spec.App.Otk.DmzAuthSecret) - } - - // Build endpoint URL for external gateway - // Format: :/graphman - // ApplyGraphmanBundle expects format: host:port/path (without https://) - gatewayReference := gateway.Spec.App.Otk.DmzOtkGatewayReference - port := gateway.Spec.App.Otk.DmzGatewayPort - if port == 0 { - port = 9443 // Default graphman port - } - - // For external gateways, the reference might be a hostname or IP - endpoint := fmt.Sprintf("%s:%d/graphman", gatewayReference, port) - - params.Log.V(2).Info("Syncing Internal certificate to external DMZ gateway", - "gateway", gatewayReference, - "endpoint", endpoint, - "sha1Sum", sha1Sum) - - // Apply bundle to external gateway using graphman - err = util.ApplyGraphmanBundle(username, password, endpoint, "", bundleBytes) - if err != nil { - return fmt.Errorf("failed to sync Internal certificate to external DMZ gateway: %w", err) - } - - params.Log.Info("Successfully synced Internal certificate to external DMZ gateway", - "gateway", gatewayReference, - "endpoint", endpoint, - "sha1Sum", sha1Sum) - - return nil -} - -// calculateCertThumbprint calculates the SHA1 thumbprint of a certificate in the format expected by Graphman -// Format: base64-encoded hex string of SHA1 fingerprint -func calculateCertThumbprint(rawCert []byte) (string, error) { - fingerprint := sha1.Sum(rawCert) - var buf bytes.Buffer - for _, f := range fingerprint { - fmt.Fprintf(&buf, "%02X", f) - } - hexDump, err := hex.DecodeString(buf.String()) - if err != nil { - return "", err - } - buf.Reset() - return base64.StdEncoding.EncodeToString(hexDump), nil -} diff --git a/pkg/gateway/reconcile/l7otkpolicies.go b/pkg/gateway/reconcile/l7otkpolicies.go deleted file mode 100644 index 7e9b2f66..00000000 --- a/pkg/gateway/reconcile/l7otkpolicies.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -* Copyright (c) 2025 Broadcom. All rights reserved. -* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. -* All trademarks, trade names, service marks, and logos referenced -* herein belong to their respective companies. -* -* This software and all information contained therein is confidential -* and proprietary and shall not be duplicated, used, disclosed or -* disseminated in any way except as authorized by the applicable -* license agreement, without the express written permission of Broadcom. -* All authorized reproductions must be marked with this language. -* -* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE -* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS -* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION -* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, -* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO -* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR -* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, -* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR -* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE -* POSSIBILITY OF SUCH LOSS OR DAMAGE. -* - */ -package reconcile - -import ( - "context" - "strconv" - "strings" - - securityv1 "github.com/caapim/layer7-operator/api/v1" - "github.com/caapim/layer7-operator/pkg/util" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" -) - -func syncOtkPolicies(ctx context.Context, params Params) { - gateway := &securityv1.Gateway{} - err := params.Client.Get(ctx, types.NamespacedName{Name: params.Instance.Name, Namespace: params.Instance.Namespace}, gateway) - if err != nil && k8serrors.IsNotFound(err) { - params.Log.Error(err, "gateway not found", "name", params.Instance.Name, "namespace", params.Instance.Namespace) - _ = removeJob(params.Instance.Name + "-" + params.Instance.Namespace + "-sync-otk-policies") - return - } - - if !gateway.Spec.App.Otk.Enabled { - _ = removeJob(params.Instance.Name + "-" + params.Instance.Namespace + "-sync-otk-policies") - return - } - - params.Instance = gateway - - err = applyOtkPolicies(ctx, params, gateway) - if err != nil { - params.Log.Error(err, "failed to reconcile otk policies", "name", gateway.Name, "namespace", gateway.Namespace) - } - -} - -func applyOtkPolicies(ctx context.Context, params Params, gateway *securityv1.Gateway) error { - internalGatewayPort := 9443 - defaultOtkPort := 8443 - if gateway.Spec.App.Management.Graphman.DynamicSyncPort != 0 { - internalGatewayPort = gateway.Spec.App.Management.Graphman.DynamicSyncPort - } - - if gateway.Spec.App.Otk.OTKPort != 0 { - defaultOtkPort = gateway.Spec.App.Otk.OTKPort - } - - var gatewayHost string - switch gateway.Spec.App.Otk.Type { - case securityv1.OtkTypeDMZ: - // TODO: open this to internal gateways that are fully external or in a different namespace - // This routes via 9443 or the management port by default - if gateway.Spec.App.Otk.InternalGatewayPort != 0 { - internalGatewayPort = gateway.Spec.App.Otk.InternalGatewayPort - } - gatewayHost = "https://" + gateway.Spec.App.Otk.InternalOtkGatewayReference + ":" + strconv.Itoa(internalGatewayPort) - case securityv1.OtkTypeInternal: - gatewayHost = "https://" + gateway.Name + ":" + strconv.Itoa(defaultOtkPort) - } - - bundle, sha1Sum, err := util.BuildOtkOverrideBundle(strings.ToUpper(string(gateway.Spec.App.Otk.Type)), gatewayHost, defaultOtkPort) - if err != nil { - return err - } - - name := gateway.Name - if gateway.Spec.App.Management.SecretName != "" { - name = gateway.Spec.App.Management.SecretName - } - gwSecret, err := getGatewaySecret(ctx, params, name) - - if err != nil { - return err - } - - annotation := "security.brcmlabs.com/" + gateway.Name + "-" + string(gateway.Spec.App.Otk.Type) + "-policies" - - if !gateway.Spec.App.Management.Database.Enabled { - podList, err := getGatewayPods(ctx, params) - if err != nil { - return err - } - err = ReconcileEphemeralGateway(ctx, params, "otk policies", *podList, gateway, gwSecret, "", annotation, sha1Sum, false, "otk policies", bundle) - if err != nil { - return err - } - } else { - gatewayDeployment, err := getGatewayDeployment(ctx, params) - if err != nil { - return err - } - err = ReconcileDBGateway(ctx, params, "otk policies", *gatewayDeployment, gateway, gwSecret, "", annotation, sha1Sum, false, "otk policies", bundle) - if err != nil { - return err - } - } - - return nil -} From ed61b0705e7646db464a749ec05d15ea9890915f Mon Sep 17 00:00:00 2001 From: Gary Vermeulen Date: Thu, 26 Mar 2026 10:51:42 +0000 Subject: [PATCH 11/11] updated otk dual modesupport --- Makefile | 2 +- api/v1/gateway_types.go | 17 +- api/v1/zz_generated.deepcopy.go | 36 ++ .../bases/security.brcmlabs.com_gateways.yaml | 45 +- deploy/bundle.yaml | 86 ++- deploy/crd.yaml | 86 ++- deploy/cw-bundle.yaml | 86 ++- docs/gateway.md | 191 ++++++- example/Makefile | 42 ++ .../gateway/otk/README-DUAL-GATEWAY-OTK.md | 498 ----------------- example/otk/dual/README.md | 500 +++++++++++++++++- internal/controller/gateway/controller.go | 6 +- pkg/gateway/deployment.go | 72 ++- pkg/gateway/reconcile/deployment.go | 12 + pkg/gateway/reconcile/gateway.go | 143 +++++ pkg/gateway/reconcile/otkfipscerts.go | 69 +++ pkg/gateway/reconcile/secret.go | 36 ++ pkg/gateway/secrets.go | 106 ++++ pkg/util/graphman.go | 189 ++++++- 19 files changed, 1600 insertions(+), 622 deletions(-) delete mode 100644 example/gateway/otk/README-DUAL-GATEWAY-OTK.md create mode 100644 pkg/gateway/reconcile/otkfipscerts.go diff --git a/Makefile b/Makefile index 04fd8906..6e1c8336 100644 --- a/Makefile +++ b/Makefile @@ -222,7 +222,7 @@ run: manifests generate fmt vet ## Run a controller from your host. .PHONY: docker-build docker-build: dockerfile #test ## Build docker image with the manager. - $(CONTAINER_TOOL) build -f operator.Dockerfile -t ${IMG} . + $(CONTAINER_TOOL) build -t ${IMG} -f operator.Dockerfile --build-arg COPYRIGHT="${COPYRIGHT}" --build-arg AUTHOR="layer7" --build-arg TITLE="layer7-operator" --build-arg VERSION="${IMAGE_TAG}" --build-arg CREATED="${CREATED}" . .PHONY: docker-push docker-push: ## Push docker image with the manager. diff --git a/api/v1/gateway_types.go b/api/v1/gateway_types.go index c184daf1..d3434fd6 100644 --- a/api/v1/gateway_types.go +++ b/api/v1/gateway_types.go @@ -117,6 +117,8 @@ type GatewayStatus struct { LastAppliedExternalSecrets map[string][]string `json:"lastAppliedExternalSecrets,omitempty"` // LastAppliedExternalCerts LastAppliedExternalCerts map[string][]string `json:"lastAppliedExternalCerts,omitempty"` + // LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates have been applied + LastAppliedOtkFipsCerts map[string][]string `json:"lastAppliedOtkFipsCerts,omitempty"` } // GatewayState tracks the status of Gateway Resources @@ -367,6 +369,19 @@ type Otk struct { InternalOTKGateway GatewayReference `json:"internalGateway,omitempty"` //DmzOTKGateway reference if type is internal DmzOTKGateway GatewayReference `json:"dmzGateway,omitempty"` + // FipsCertificates is a list of certificate references for FIPS user management + // on Internal gateways. Each entry references a Secret or ConfigMap containing + // leaf certificates for DMZ gateway mTLS client authentication. + FipsCertificates []OtkFipsCertificate `json:"fipsCertificates,omitempty"` +} + +type OtkFipsCertificate struct { + // Enabled or disabled + Enabled bool `json:"enabled,omitempty"` + // Name of the Secret or ConfigMap + Name string `json:"name,omitempty"` + // Type of the referenced resource: "secret" or "configmap" + Type string `json:"type,omitempty"` } // OtkMaintenanceTasks are included in the install bundle as disabled scheduled tasks @@ -384,7 +399,7 @@ type GatewayReference struct { Namespace string `json:"namespace,omitempty"` // Url of the target gateway // used for post-installation gateway policy configuration - Url string `json:"Url,omitempty"` + Url string `json:"url,omitempty"` // Port of the target gateway Port int `json:"port,omitempty"` } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index e1dac776..372d0911 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -721,6 +721,22 @@ func (in *GatewayStatus) DeepCopyInto(out *GatewayStatus) { (*out)[key] = outVal } } + if in.LastAppliedOtkFipsCerts != nil { + in, out := &in.LastAppliedOtkFipsCerts, &out.LastAppliedOtkFipsCerts + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayStatus. @@ -1052,6 +1068,11 @@ func (in *Otk) DeepCopyInto(out *Otk) { out.MaintenanceTasks = in.MaintenanceTasks out.InternalOTKGateway = in.InternalOTKGateway out.DmzOTKGateway = in.DmzOTKGateway + if in.FipsCertificates != nil { + in, out := &in.FipsCertificates, &out.FipsCertificates + *out = make([]OtkFipsCertificate, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Otk. @@ -1140,6 +1161,21 @@ func (in *OtkDatabaseAuthCredentials) DeepCopy() *OtkDatabaseAuthCredentials { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OtkFipsCertificate) DeepCopyInto(out *OtkFipsCertificate) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OtkFipsCertificate. +func (in *OtkFipsCertificate) DeepCopy() *OtkFipsCertificate { + if in == nil { + return nil + } + out := new(OtkFipsCertificate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OtkMaintenanceTasks) DeepCopyInto(out *OtkMaintenanceTasks) { *out = *in diff --git a/config/crd/bases/security.brcmlabs.com_gateways.yaml b/config/crd/bases/security.brcmlabs.com_gateways.yaml index 34d4b9ce..10347579 100644 --- a/config/crd/bases/security.brcmlabs.com_gateways.yaml +++ b/config/crd/bases/security.brcmlabs.com_gateways.yaml @@ -4006,11 +4006,6 @@ spec: dmzGateway: description: DmzOTKGateway reference if type is internal properties: - Url: - description: |- - Url of the target gateway - used for post-installation gateway policy... - type: string name: description: |- Name of the gateway @@ -4023,10 +4018,32 @@ spec: port: description: Port of the target gateway type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string type: object enabled: description: Enable or disable the OTK initContainer type: boolean + fipsCertificates: + description: FipsCertificates is a list of certificate references + for FIPS user... + items: + properties: + enabled: + description: Enabled or disabled + type: boolean + name: + description: Name of the Secret or ConfigMap + type: string + type: + description: 'Type of the referenced resource: "secret" + or "configmap"' + type: string + type: object + type: array initContainerImage: description: InitContainerImage for the initContainer type: string @@ -4163,11 +4180,6 @@ spec: internalGateway: description: InternalOTKGateway reference if type is dmz properties: - Url: - description: |- - Url of the target gateway - used for post-installation gateway policy... - type: string name: description: |- Name of the gateway @@ -4180,6 +4192,11 @@ spec: port: description: Port of the target gateway type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string type: object maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled @@ -6510,6 +6527,14 @@ spec: items: type: string type: array + lastAppliedOtkFipsCerts: + additionalProperties: + items: + type: string + type: array + description: LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates + have been... + type: object managementPod: description: Management Pod is a Gateway with a special annotation is used as a... diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 80400590..3b94dc9a 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -4002,12 +4002,47 @@ spec: description: Type of OTK Database type: string type: object - dmzGatewayReference: - description: OTKPort is used in Single mode - sets the otk. - type: string + dmzGateway: + description: DmzOTKGateway reference if type is internal + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object enabled: description: Enable or disable the OTK initContainer type: boolean + fipsCertificates: + description: FipsCertificates is a list of certificate references + for FIPS user... + items: + properties: + enabled: + description: Enabled or disabled + type: boolean + name: + description: Name of the Secret or ConfigMap + type: string + type: + description: 'Type of the referenced resource: "secret" + or "configmap"' + type: string + type: object + type: array initContainerImage: description: InitContainerImage for the initContainer type: string @@ -4141,14 +4176,27 @@ spec: type: string type: object type: object - internalGatewayPort: - description: InternalGatewayPort defaults to 9443 or graphmanDynamicSync - port - type: integer - internalGatewayReference: - description: InternalOtkGatewayReference to an Operator managed - Gateway deployment that... - type: string + internalGateway: + description: InternalOTKGateway reference if type is dmz + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled by default @@ -4157,6 +4205,10 @@ spec: description: Enable or disable database maintenance tasks type: boolean type: object + manageCrossNamespace: + description: ManageCrossNamespace allows a cluster-wide layer7 + operator to manage... + type: boolean overrides: description: Overrides default OTK install functionality properties: @@ -4191,10 +4243,6 @@ spec: port: description: OTKPort defaults to 8443 type: integer - runtimeSyncIntervalSeconds: - description: RuntimeSyncIntervalSeconds how often OTK Gateways - should be updated in... - type: integer subSolutionKitNames: description: A list of subSolutionKitNames - all,internal or dmz cover the primary use... @@ -6478,6 +6526,14 @@ spec: items: type: string type: array + lastAppliedOtkFipsCerts: + additionalProperties: + items: + type: string + type: array + description: LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates + have been... + type: object managementPod: description: Management Pod is a Gateway with a special annotation is used as a... diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 0596a22c..179219a0 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -4002,12 +4002,47 @@ spec: description: Type of OTK Database type: string type: object - dmzGatewayReference: - description: OTKPort is used in Single mode - sets the otk. - type: string + dmzGateway: + description: DmzOTKGateway reference if type is internal + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object enabled: description: Enable or disable the OTK initContainer type: boolean + fipsCertificates: + description: FipsCertificates is a list of certificate references + for FIPS user... + items: + properties: + enabled: + description: Enabled or disabled + type: boolean + name: + description: Name of the Secret or ConfigMap + type: string + type: + description: 'Type of the referenced resource: "secret" + or "configmap"' + type: string + type: object + type: array initContainerImage: description: InitContainerImage for the initContainer type: string @@ -4141,14 +4176,27 @@ spec: type: string type: object type: object - internalGatewayPort: - description: InternalGatewayPort defaults to 9443 or graphmanDynamicSync - port - type: integer - internalGatewayReference: - description: InternalOtkGatewayReference to an Operator managed - Gateway deployment that... - type: string + internalGateway: + description: InternalOTKGateway reference if type is dmz + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled by default @@ -4157,6 +4205,10 @@ spec: description: Enable or disable database maintenance tasks type: boolean type: object + manageCrossNamespace: + description: ManageCrossNamespace allows a cluster-wide layer7 + operator to manage... + type: boolean overrides: description: Overrides default OTK install functionality properties: @@ -4191,10 +4243,6 @@ spec: port: description: OTKPort defaults to 8443 type: integer - runtimeSyncIntervalSeconds: - description: RuntimeSyncIntervalSeconds how often OTK Gateways - should be updated in... - type: integer subSolutionKitNames: description: A list of subSolutionKitNames - all,internal or dmz cover the primary use... @@ -6478,6 +6526,14 @@ spec: items: type: string type: array + lastAppliedOtkFipsCerts: + additionalProperties: + items: + type: string + type: array + description: LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates + have been... + type: object managementPod: description: Management Pod is a Gateway with a special annotation is used as a... diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 1be27732..fd2d446d 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -4011,12 +4011,47 @@ spec: description: Type of OTK Database type: string type: object - dmzGatewayReference: - description: OTKPort is used in Single mode - sets the otk. - type: string + dmzGateway: + description: DmzOTKGateway reference if type is internal + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object enabled: description: Enable or disable the OTK initContainer type: boolean + fipsCertificates: + description: FipsCertificates is a list of certificate references + for FIPS user... + items: + properties: + enabled: + description: Enabled or disabled + type: boolean + name: + description: Name of the Secret or ConfigMap + type: string + type: + description: 'Type of the referenced resource: "secret" + or "configmap"' + type: string + type: object + type: array initContainerImage: description: InitContainerImage for the initContainer type: string @@ -4150,14 +4185,27 @@ spec: type: string type: object type: object - internalGatewayPort: - description: InternalGatewayPort defaults to 9443 or graphmanDynamicSync - port - type: integer - internalGatewayReference: - description: InternalOtkGatewayReference to an Operator managed - Gateway deployment that... - type: string + internalGateway: + description: InternalOTKGateway reference if type is dmz + properties: + name: + description: |- + Name of the gateway + if managing otk gateways across namespaces this must... + type: string + namespace: + description: Namespace of the referenced gateway if managing + gateways cross namespace... + type: string + port: + description: Port of the target gateway + type: integer + url: + description: |- + Url of the target gateway + used for post-installation gateway policy... + type: string + type: object maintenanceTasks: description: MaintenanceTasks for the OTK database are disabled by default @@ -4166,6 +4214,10 @@ spec: description: Enable or disable database maintenance tasks type: boolean type: object + manageCrossNamespace: + description: ManageCrossNamespace allows a cluster-wide layer7 + operator to manage... + type: boolean overrides: description: Overrides default OTK install functionality properties: @@ -4200,10 +4252,6 @@ spec: port: description: OTKPort defaults to 8443 type: integer - runtimeSyncIntervalSeconds: - description: RuntimeSyncIntervalSeconds how often OTK Gateways - should be updated in... - type: integer subSolutionKitNames: description: A list of subSolutionKitNames - all,internal or dmz cover the primary use... @@ -6487,6 +6535,14 @@ spec: items: type: string type: array + lastAppliedOtkFipsCerts: + additionalProperties: + items: + type: string + type: array + description: LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates + have been... + type: object managementPod: description: Management Pod is a Gateway with a special annotation is used as a... diff --git a/docs/gateway.md b/docs/gateway.md index cd245c6b..9c7f619c 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -10634,11 +10634,10 @@ This can be further extended with custom attributes using the additionalResource false - dmzGatewayReference - string + dmzGateway + object - OTKPort is used in Single mode - sets the otk.port cluster-wide property and in Dual-Mode -sets host_oauth2_auth_server port in #OTK Client Context Variables
+ DmzOTKGateway reference if type is internal
false @@ -10648,6 +10647,15 @@ sets host_oauth2_auth_server port in #OTK Client Context Variables
Enable or disable the OTK initContainer
false + + fipsCertificates + []object + + FipsCertificates is a list of certificate references for FIPS user management +on Internal gateways. Each entry references a Secret or ConfigMap containing +leaf certificates for DMZ gateway mTLS client authentication.
+ + false initContainerImage string @@ -10670,25 +10678,25 @@ sets host_oauth2_auth_server port in #OTK Client Context Variables
false - internalGatewayPort - integer + internalGateway + object - InternalGatewayPort defaults to 9443 or graphmanDynamicSync port
+ InternalOTKGateway reference if type is dmz
false - internalGatewayReference - string + maintenanceTasks + object - InternalOtkGatewayReference to an Operator managed Gateway deployment that is configured with otk.type: internal -This configures a relationship between DMZ and Internal Gateways.
+ MaintenanceTasks for the OTK database are disabled by default
false - maintenanceTasks - object + manageCrossNamespace + boolean - MaintenanceTasks for the OTK database are disabled by default
+ ManageCrossNamespace allows a cluster-wide layer7 operator to manage internal/dmz gateways across namespaces +this is limited to a single kubernetes cluster.
false @@ -10705,13 +10713,6 @@ This configures a relationship between DMZ and Internal Gateways.
OTKPort defaults to 8443
false - - runtimeSyncIntervalSeconds - integer - - RuntimeSyncIntervalSeconds how often OTK Gateways should be updated in internal/dmz mode
- - false subSolutionKitNames []string @@ -11313,6 +11314,97 @@ only supports MySQL and Oracle
+### Gateway.spec.app.otk.dmzGateway +[↩ Parent](#gatewayspecappotk) + + + +DmzOTKGateway reference if type is internal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the gateway +if managing otk gateways across namespaces this must match the referenced gateway CR
+
false
namespacestring + Namespace of the referenced gateway if managing gateways cross namespace (optional)
+
false
portinteger + Port of the target gateway
+
false
urlstring + Url of the target gateway +used for post-installation gateway policy configuration
+
false
+ + +### Gateway.spec.app.otk.fipsCertificates[index] +[↩ Parent](#gatewayspecappotk) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
enabledboolean + Enabled or disabled
+
false
namestring + Name of the Secret or ConfigMap
+
false
typestring + Type of the referenced resource: "secret" or "configmap"
+
false
+ + ### Gateway.spec.app.otk.initContainerSecurityContext [↩ Parent](#gatewayspecappotk) @@ -11698,6 +11790,56 @@ PodSecurityContext, the value specified in SecurityContext takes precedence.
+### Gateway.spec.app.otk.internalGateway +[↩ Parent](#gatewayspecappotk) + + + +InternalOTKGateway reference if type is dmz + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of the gateway +if managing otk gateways across namespaces this must match the referenced gateway CR
+
false
namespacestring + Namespace of the referenced gateway if managing gateways cross namespace (optional)
+
false
portinteger + Port of the target gateway
+
false
urlstring + Url of the target gateway +used for post-installation gateway policy configuration
+
false
+ + ### Gateway.spec.app.otk.maintenanceTasks [↩ Parent](#gatewayspecappotk) @@ -17951,6 +18093,13 @@ GatewayStatus defines the observed state of Gateways LastAppliedClusterProperties
false + + lastAppliedOtkFipsCerts + map[string][]string + + LastAppliedOtkFipsCerts tracks which OTK FIPS user certificates have been applied
+ + false managementPod string diff --git a/example/Makefile b/example/Makefile index 7eab6a00..f29739fa 100644 --- a/example/Makefile +++ b/example/Makefile @@ -220,6 +220,48 @@ pki: --cert=/tmp/tls.crt --key=/tmp/tls.key \ -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - +pki-otk: + @test -f /tmp/otk-internal-tls.crt || \ + openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ + -subj "/CN=gateway-internal-otk.brcmlabs.com" \ + -addext "subjectAltName=DNS:gateway-internal-otk.brcmlabs.com,DNS:gateway-internal-otk-pm.brcmlabs.com" \ + -keyout /tmp/otk-internal-tls.key -out /tmp/otk-internal-tls.crt + @test -f /tmp/otk-dmz-tls.crt || \ + openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ + -subj "/CN=gateway-dmz-otk.brcmlabs.com" \ + -addext "subjectAltName=DNS:gateway-dmz-otk.brcmlabs.com,DNS:gateway-dmz-otk-pm.brcmlabs.com" \ + -keyout /tmp/otk-dmz-tls.key -out /tmp/otk-dmz-tls.crt + @test -f /tmp/otk-dmz-mtls.crt || { \ + openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ + -subj "/CN=otk-dmz-mtls-ca" \ + -keyout /tmp/otk-dmz-mtls-ca.key -out /tmp/otk-dmz-mtls-ca.crt && \ + openssl req -newkey rsa:2048 -nodes \ + -subj "/CN=dmz-gateway" \ + -keyout /tmp/otk-dmz-mtls.key -out /tmp/otk-dmz-mtls.csr && \ + openssl x509 -req -in /tmp/otk-dmz-mtls.csr \ + -CA /tmp/otk-dmz-mtls-ca.crt -CAkey /tmp/otk-dmz-mtls-ca.key \ + -CAcreateserial -days 365 -out /tmp/otk-dmz-mtls.crt; \ + } + +pki-otk-dmz: pki-otk + -kubectl create secret tls otk-dmz-secret \ + --cert=/tmp/otk-dmz-tls.crt --key=/tmp/otk-dmz-tls.key \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + -kubectl create secret tls otk-dmz-mtls-secret \ + --cert=/tmp/otk-dmz-mtls.crt --key=/tmp/otk-dmz-mtls.key \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + -kubectl create secret generic otk-internal-ca-cert \ + --from-file=ca.crt=/tmp/otk-internal-tls.crt \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + +pki-otk-internal: pki-otk + -kubectl create secret tls otk-internal-secret \ + --cert=/tmp/otk-internal-tls.crt --key=/tmp/otk-internal-tls.key \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + -kubectl create secret generic otk-dmz-mtls-public-cert \ + --from-file=dmz-gateway.crt=/tmp/otk-dmz-mtls.crt \ + -n $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f - + cassandra: helm upgrade --install cassandra -f ./otk/database/cassandra/cassandra-values.yaml oci://registry-1.docker.io/bitnamicharts/cassandra diff --git a/example/gateway/otk/README-DUAL-GATEWAY-OTK.md b/example/gateway/otk/README-DUAL-GATEWAY-OTK.md deleted file mode 100644 index 7e42ea61..00000000 --- a/example/gateway/otk/README-DUAL-GATEWAY-OTK.md +++ /dev/null @@ -1,498 +0,0 @@ -# Dual Gateway OTK Configuration Guide - -This guide describes how to configure a Dual Gateway OAuth Toolkit (OTK) deployment using the Layer7 Gateway Operator. In a dual gateway setup, one gateway acts as the DMZ (Demilitarized Zone) gateway and another acts as the Internal gateway, providing enhanced security and separation of concerns. - -## Table of Contents - -- [Overview](#overview) -- [Architecture](#architecture) -- [Prerequisites](#prerequisites) -- [Configuration Overview](#configuration-overview) -- [Step 1: Create Required Secrets](#step-1-create-required-secrets) -- [Step 2: Configure DMZ Gateway](#step-2-configure-dmz-gateway) -- [Step 3: Configure Internal Gateway](#step-3-configure-internal-gateway) -- [Key Configuration Fields](#key-configuration-fields) -- [Deployment](#deployment) -- [Certificate Synchronization](#certificate-synchronization) -- [External Gateway Support](#external-gateway-support) -- [Troubleshooting](#troubleshooting) - -## Overview - -The Dual Gateway OTK deployment consists of: - -- **DMZ Gateway**: Handles external client requests and acts as the OAuth authorization server -- **Internal Gateway**: Handles token validation and resource server operations - -The operator automatically synchronizes certificates and keys between the two gateways, ensuring secure communication and proper OAuth flow. - -## Configuration Overview - -The dual gateway setup requires: - -1. **TLS Secrets**: For DMZ and Internal gateway keys/certificates -2. **Auth Secrets**: For gateway authentication credentials -3. **DMZ Gateway Configuration**: With `type: dmz` -4. **Internal Gateway Configuration**: With `type: internal` - -## Step 1: Create Required Secrets - -### Create TLS Secrets - -You need to create TLS secrets for both DMZ and Internal gateways. These secrets must be of type `kubernetes.io/tls` and contain: -- `tls.crt`: The certificate -- `tls.key`: The private key - -#### Option 1: Using the Provided Script - -A helper script is available to generate self-signed certificates and create all required secrets: - -```bash -cd example/gateway/otk/secrets -./create-secrets.sh -``` - -This script creates: -- `otk-dmz-tls-secret`: TLS secret for DMZ gateway -- `otk-internal-tls-secret`: TLS secret for Internal gateway -- `otk-dmz-auth-secret`: Authentication secret for DMZ gateway (username: `admin`, password: `7layer`) -- `otk-internal-auth-secret`: Authentication secret for Internal gateway (username: `admin`, password: `7layer`) - -#### Option 2: Manual Secret Creation - -**DMZ TLS Secret:** - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: otk-dmz-tls-secret - namespace: default -type: kubernetes.io/tls -data: - tls.crt: - tls.key: -``` - -**Internal TLS Secret:** - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: otk-internal-tls-secret - namespace: default -type: kubernetes.io/tls -data: - tls.crt: - tls.key: -``` - -**DMZ Auth Secret:** - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: otk-dmz-auth-secret - namespace: default -type: Opaque -stringData: - SSG_ADMIN_USERNAME: admin - SSG_ADMIN_PASSWORD: 7layer -``` - -**Internal Auth Secret:** - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: otk-internal-auth-secret - namespace: default -type: Opaque -stringData: - SSG_ADMIN_USERNAME: admin - SSG_ADMIN_PASSWORD: 7layer -``` - -## Step 2: Configure DMZ Gateway - -The DMZ gateway configuration should include: - -- `otk.type: dmz` -- Reference to Internal gateway -- DMZ key secret reference -- Internal auth secret for communication with Internal gateway -- Database configuration - -### Sample DMZ Gateway Configuration - -```yaml -apiVersion: security.brcmlabs.com/v1 -kind: Gateway -metadata: - name: otk-ssg-dmz -spec: - version: "11.1.3" - license: - accept: true - secretName: gateway-license - app: - replicas: 1 - image: docker.io/caapim/gateway:11.1.3 - imagePullPolicy: IfNotPresent - resources: - requests: - memory: 8Gi - cpu: 3 - limits: - memory: 8Gi - cpu: 3 - # ExternalKeys with otk flag set to true for OTK-specific key usage - externalKeys: - - name: otk-dmz-tls-secret - enabled: true - alias: otk-dmz-key - keyUsageType: SSL - otk: true - otk: - enabled: true - initContainerImage: docker.io/caapim/otk-install:4.6.4 - type: dmz - # Reference to Internal gateway (can be Gateway name or external hostname) - internalGatewayReference: otk-ssg-internal - # InternalGatewayPort is used when the Internal gateway is external - # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port - internalGatewayPort: 9443 - # SyncIntervalSeconds determines how often certificates are synchronized - # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set - syncIntervalSeconds: 30 - # Reference to the TLS secret for DMZ key - dmzKeySecret: otk-dmz-tls-secret - # Auth secret for Internal gateway communication - internalAuthSecret: otk-internal-auth-secret - database: - type: mysql - create: true - connectionName: OAuth - auth: - gateway: - username: otk_user - password: otkUserPass - readOnly: - username: readonly_user - password: readonly_userPass - admin: - username: admin - password: adminPass - properties: - minimumPoolSize: 3 - maximumPoolSize: 15 - sql: - databaseName: otk_db - jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init - jdbcDriverClass: com.mysql.cj.jdbc.Driver - connectionProperties: - c3p0.maxConnectionAge: "100" - c3p0.maxIdleTime: "1000" - manageSchema: true - databaseWaitTimeout: 60 - management: - secretName: gateway-secret - graphman: - enabled: true - initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 - dynamicSyncPort: 9443 - cluster: - hostname: gateway.brcmlabs.com - service: - type: ClusterIP - ports: - - name: https - port: 8443 - targetPort: 8443 - protocol: TCP -``` - -## Step 3: Configure Internal Gateway - -The Internal gateway configuration should include: - -- `otk.type: internal` -- Reference to DMZ gateway -- Internal key secret reference -- DMZ auth secret for communication with DMZ gateway -- Database configuration (shared with DMZ) - -### Sample Internal Gateway Configuration - -```yaml -apiVersion: security.brcmlabs.com/v1 -kind: Gateway -metadata: - name: otk-ssg-internal -spec: - version: "11.1.3" - license: - accept: true - secretName: gateway-license - app: - replicas: 1 - image: docker.io/caapim/gateway:11.1.3 - imagePullPolicy: IfNotPresent - resources: - requests: - memory: 8Gi - cpu: 3 - limits: - memory: 8Gi - cpu: 3 - # ExternalKeys with otk flag set to true for OTK-specific key usage - externalKeys: - - name: otk-internal-tls-secret - enabled: true - alias: otk-internal-key - keyUsageType: SSL - otk: true - otk: - enabled: true - initContainerImage: docker.io/caapim/otk-install:4.6.4 - type: internal - # Reference to DMZ gateway (can be Gateway name or external hostname) - dmzGatewayReference: otk-ssg-dmz - # DmzGatewayPort is used when the DMZ gateway is external - # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port - dmzGatewayPort: 9443 - # SyncIntervalSeconds determines how often certificates are synchronized - # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set - syncIntervalSeconds: 30 - # Reference to the TLS secret for Internal key - internalKeySecret: otk-internal-tls-secret - # Auth secret for DMZ gateway communication - dmzAuthSecret: otk-dmz-auth-secret - database: - type: mysql - create: true - connectionName: OAuth - auth: - gateway: - username: otk_user - password: otkUserPass - readOnly: - username: readonly_user - password: readonly_userPass - admin: - username: admin - password: adminPass - properties: - minimumPoolSize: 3 - maximumPoolSize: 15 - sql: - databaseName: otk_db - jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init - jdbcDriverClass: com.mysql.cj.jdbc.Driver - connectionProperties: - c3p0.maxConnectionAge: "100" - c3p0.maxIdleTime: "1000" - manageSchema: true - databaseWaitTimeout: 60 - management: - secretName: gateway-secret - graphman: - enabled: true - initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 - cluster: - hostname: gateway.brcmlabs.com - service: - type: ClusterIP - ports: - - name: https - port: 8443 - targetPort: 8443 - protocol: TCP - - name: management - port: 9443 - targetPort: 9443 - protocol: TCP -``` - -## Key Configuration Fields - -### OTK-Specific Fields - -| Field | Description | Required | Default | -|-------|-------------|----------|---------| -| `otk.enabled` | Enable OTK installation | Yes | `false` | -| `otk.type` | OTK type: `dmz`, `internal`, or `single` | Yes | - | -| `otk.initContainerImage` | OTK init container image | Yes | - | -| `otk.dmzKeySecret` | Reference to TLS secret containing DMZ key/cert | Yes (DMZ) | - | -| `otk.internalKeySecret` | Reference to TLS secret containing Internal key/cert | Yes (Internal) | - | -| `otk.dmzAuthSecret` | Reference to secret with DMZ gateway credentials | Yes (Internal) | - | -| `otk.internalAuthSecret` | Reference to secret with Internal gateway credentials | Yes (DMZ) | - | -| `otk.dmzGatewayReference` | Reference to DMZ gateway (name or hostname) | Yes (Internal) | - | -| `otk.internalGatewayReference` | Reference to Internal gateway (name or hostname) | Yes (DMZ) | - | -| `otk.dmzGatewayPort` | Port for DMZ gateway (when external) | No | `9443` or `graphmanDynamicSync` port | -| `otk.internalGatewayPort` | Port for Internal gateway (when external) | No | `9443` or `graphmanDynamicSync` port | -| `otk.syncIntervalSeconds` | Certificate sync interval in seconds | No | `RuntimeSyncIntervalSeconds` or `10` | -| `otk.port` | OTK port (defaults to 8443) | No | `8443` | - -### External Keys Configuration - -Both gateways must have `externalKeys` configured with the `otk: true` flag: - -```yaml -externalKeys: -- name: otk-dmz-tls-secret # or otk-internal-tls-secret - enabled: true - alias: otk-dmz-key # or otk-internal-key - keyUsageType: SSL - otk: true # Required for OTK key handling -``` - -## Deployment - -### 1. Create Secrets - -```bash -cd example/gateway/otk/secrets -./create-secrets.sh default -``` - -### 2. Deploy DMZ Gateway - -```bash -kubectl apply -f example/gateway/otk/otk-ssg-dmz.yaml -``` - -### 3. Deploy Internal Gateway - -```bash -kubectl apply -f example/gateway/otk/otk-ssg-internal.yaml -``` - -### 4. Verify Deployment - -```bash -# Check gateway pods -kubectl get pods -l app=gateway - -# Check gateway status -kubectl get gateway otk-ssg-dmz -kubectl get gateway otk-ssg-internal - -# Check logs -kubectl logs -l app=gateway,gateway-name=otk-ssg-dmz -kubectl logs -l app=gateway,gateway-name=otk-ssg-internal -``` - -## Certificate Synchronization - -The operator automatically synchronizes certificates between DMZ and Internal gateways: - -1. **DMZ Certificate → Internal Gateway**: When the DMZ certificate is updated, it's automatically published to the Internal gateway as a trusted certificate and used for FIP (Federated Identity Provider) user creation. - -2. **Internal Certificate → DMZ Gateway**: When the Internal certificate is updated, it's automatically published to the DMZ gateway as a trusted certificate. - -3. **Sync Interval**: Controlled by `syncIntervalSeconds` (default: 10 seconds or `RuntimeSyncIntervalSeconds`). - -4. **Key Updates**: When DMZ or Internal keys are updated: - - The key is synchronized to the respective gateway - - The DMZ private key name is updated in the cluster-wide property `otk.dmz.private_key.name` (DMZ gateway only) - - Old certificates are removed before new ones are published - -## External Gateway Support - -The operator supports scenarios where one or both gateways are external (not managed by the operator): - -### External DMZ Gateway - -If the DMZ gateway is external, configure the Internal gateway with: - -```yaml -otk: - type: internal - dmzGatewayReference: external-dmz-gateway.example.com - dmzGatewayPort: 9443 # Port for Graphman API - dmzAuthSecret: otk-dmz-auth-secret -``` - -### External Internal Gateway - -If the Internal gateway is external, configure the DMZ gateway with: - -```yaml -otk: - type: dmz - internalGatewayReference: external-internal-gateway.example.com - internalGatewayPort: 9443 # Port for Graphman API - internalAuthSecret: otk-internal-auth-secret -``` - -### External Gateway Requirements - -- Graphman API must be enabled and accessible -- Authentication credentials must be provided via auth secrets -- The correct port must be specified if different from default (9443) -- The gateway must be reachable from the operator's network - -## Troubleshooting - -### Common Issues - -1. **Certificates not synchronizing** - - Verify `syncIntervalSeconds` is set appropriately - - Check that Graphman is enabled on both gateways - - Verify auth secrets are correctly configured - - Check operator logs for errors - -2. **Gateway communication failures** - - Verify gateway references are correct (name or hostname) - - Check network connectivity between gateways - - Verify ports are correctly configured - - Ensure auth secrets contain valid credentials - -3. **Key update failures** - - Verify TLS secrets are of type `kubernetes.io/tls` - - Check that secrets contain both `tls.crt` and `tls.key` - - Ensure `externalKeys` have `otk: true` flag - - Verify alias matches the expected value - -4. **Database connection issues** - - Verify database credentials in `otk.database.auth` - - Check JDBC URL is correct and accessible - - Ensure database exists or `create: true` is set - - Verify database wait timeout is sufficient - -### Checking Certificate Sync Status - -```bash -# Check annotations for certificate thumbprints -kubectl get gateway otk-ssg-dmz -o jsonpath='{.metadata.annotations}' -kubectl get gateway otk-ssg-internal -o jsonpath='{.metadata.annotations}' - -# Check cluster-wide properties -kubectl exec -it -- /opt/SecureSpan/Gateway/node/default/bin/ssgconfig \ - get cluster-wide-properties | grep otk.dmz.private_key.name -``` - -### Operator Logs - -```bash -# View operator logs -kubectl logs -n -l control-plane=controller-manager - -# Filter for OTK-related logs -kubectl logs -n -l control-plane=controller-manager | grep -i otk -``` - -## Additional Resources - -- [Layer7 Gateway Operator Documentation](https://github.com/broadcom/layer7-operator) -- [OAuth Toolkit Documentation](https://techdocs.broadcom.com/us/en/ca-enterprise-software/layer7-api-management/api-gateway/11-1.html) -- Example configurations: `example/gateway/otk/` - ---- - -**Note**: This configuration guide assumes you have a working Kubernetes cluster with the Layer7 Gateway Operator installed. Adjust namespaces, hostnames, and other values according to your environment. - diff --git a/example/otk/dual/README.md b/example/otk/dual/README.md index 1da54d76..7e42ea61 100644 --- a/example/otk/dual/README.md +++ b/example/otk/dual/README.md @@ -1,2 +1,498 @@ -# OTK Dual Mode Install -This mode is currently not supported with the Layer7 Operator \ No newline at end of file +# Dual Gateway OTK Configuration Guide + +This guide describes how to configure a Dual Gateway OAuth Toolkit (OTK) deployment using the Layer7 Gateway Operator. In a dual gateway setup, one gateway acts as the DMZ (Demilitarized Zone) gateway and another acts as the Internal gateway, providing enhanced security and separation of concerns. + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Prerequisites](#prerequisites) +- [Configuration Overview](#configuration-overview) +- [Step 1: Create Required Secrets](#step-1-create-required-secrets) +- [Step 2: Configure DMZ Gateway](#step-2-configure-dmz-gateway) +- [Step 3: Configure Internal Gateway](#step-3-configure-internal-gateway) +- [Key Configuration Fields](#key-configuration-fields) +- [Deployment](#deployment) +- [Certificate Synchronization](#certificate-synchronization) +- [External Gateway Support](#external-gateway-support) +- [Troubleshooting](#troubleshooting) + +## Overview + +The Dual Gateway OTK deployment consists of: + +- **DMZ Gateway**: Handles external client requests and acts as the OAuth authorization server +- **Internal Gateway**: Handles token validation and resource server operations + +The operator automatically synchronizes certificates and keys between the two gateways, ensuring secure communication and proper OAuth flow. + +## Configuration Overview + +The dual gateway setup requires: + +1. **TLS Secrets**: For DMZ and Internal gateway keys/certificates +2. **Auth Secrets**: For gateway authentication credentials +3. **DMZ Gateway Configuration**: With `type: dmz` +4. **Internal Gateway Configuration**: With `type: internal` + +## Step 1: Create Required Secrets + +### Create TLS Secrets + +You need to create TLS secrets for both DMZ and Internal gateways. These secrets must be of type `kubernetes.io/tls` and contain: +- `tls.crt`: The certificate +- `tls.key`: The private key + +#### Option 1: Using the Provided Script + +A helper script is available to generate self-signed certificates and create all required secrets: + +```bash +cd example/gateway/otk/secrets +./create-secrets.sh +``` + +This script creates: +- `otk-dmz-tls-secret`: TLS secret for DMZ gateway +- `otk-internal-tls-secret`: TLS secret for Internal gateway +- `otk-dmz-auth-secret`: Authentication secret for DMZ gateway (username: `admin`, password: `7layer`) +- `otk-internal-auth-secret`: Authentication secret for Internal gateway (username: `admin`, password: `7layer`) + +#### Option 2: Manual Secret Creation + +**DMZ TLS Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-dmz-tls-secret + namespace: default +type: kubernetes.io/tls +data: + tls.crt: + tls.key: +``` + +**Internal TLS Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-internal-tls-secret + namespace: default +type: kubernetes.io/tls +data: + tls.crt: + tls.key: +``` + +**DMZ Auth Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-dmz-auth-secret + namespace: default +type: Opaque +stringData: + SSG_ADMIN_USERNAME: admin + SSG_ADMIN_PASSWORD: 7layer +``` + +**Internal Auth Secret:** + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: otk-internal-auth-secret + namespace: default +type: Opaque +stringData: + SSG_ADMIN_USERNAME: admin + SSG_ADMIN_PASSWORD: 7layer +``` + +## Step 2: Configure DMZ Gateway + +The DMZ gateway configuration should include: + +- `otk.type: dmz` +- Reference to Internal gateway +- DMZ key secret reference +- Internal auth secret for communication with Internal gateway +- Database configuration + +### Sample DMZ Gateway Configuration + +```yaml +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-dmz +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-dmz-tls-secret + enabled: true + alias: otk-dmz-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: dmz + # Reference to Internal gateway (can be Gateway name or external hostname) + internalGatewayReference: otk-ssg-internal + # InternalGatewayPort is used when the Internal gateway is external + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + internalGatewayPort: 9443 + # SyncIntervalSeconds determines how often certificates are synchronized + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for DMZ key + dmzKeySecret: otk-dmz-tls-secret + # Auth secret for Internal gateway communication + internalAuthSecret: otk-internal-auth-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + gateway: + username: otk_user + password: otkUserPass + readOnly: + username: readonly_user + password: readonly_userPass + admin: + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + management: + secretName: gateway-secret + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + dynamicSyncPort: 9443 + cluster: + hostname: gateway.brcmlabs.com + service: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP +``` + +## Step 3: Configure Internal Gateway + +The Internal gateway configuration should include: + +- `otk.type: internal` +- Reference to DMZ gateway +- Internal key secret reference +- DMZ auth secret for communication with DMZ gateway +- Database configuration (shared with DMZ) + +### Sample Internal Gateway Configuration + +```yaml +apiVersion: security.brcmlabs.com/v1 +kind: Gateway +metadata: + name: otk-ssg-internal +spec: + version: "11.1.3" + license: + accept: true + secretName: gateway-license + app: + replicas: 1 + image: docker.io/caapim/gateway:11.1.3 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: 8Gi + cpu: 3 + limits: + memory: 8Gi + cpu: 3 + # ExternalKeys with otk flag set to true for OTK-specific key usage + externalKeys: + - name: otk-internal-tls-secret + enabled: true + alias: otk-internal-key + keyUsageType: SSL + otk: true + otk: + enabled: true + initContainerImage: docker.io/caapim/otk-install:4.6.4 + type: internal + # Reference to DMZ gateway (can be Gateway name or external hostname) + dmzGatewayReference: otk-ssg-dmz + # DmzGatewayPort is used when the DMZ gateway is external + # If not specified, defaults to 9443 or the gateway's graphmanDynamicSync port + dmzGatewayPort: 9443 + # SyncIntervalSeconds determines how often certificates are synchronized + # Defaults to RuntimeSyncIntervalSeconds if not specified, or 10 seconds if neither is set + syncIntervalSeconds: 30 + # Reference to the TLS secret for Internal key + internalKeySecret: otk-internal-tls-secret + # Auth secret for DMZ gateway communication + dmzAuthSecret: otk-dmz-auth-secret + database: + type: mysql + create: true + connectionName: OAuth + auth: + gateway: + username: otk_user + password: otkUserPass + readOnly: + username: readonly_user + password: readonly_userPass + admin: + username: admin + password: adminPass + properties: + minimumPoolSize: 3 + maximumPoolSize: 15 + sql: + databaseName: otk_db + jdbcUrl: jdbc:mysql://mysql.brcmlabs.com:3306/otk_db_init + jdbcDriverClass: com.mysql.cj.jdbc.Driver + connectionProperties: + c3p0.maxConnectionAge: "100" + c3p0.maxIdleTime: "1000" + manageSchema: true + databaseWaitTimeout: 60 + management: + secretName: gateway-secret + graphman: + enabled: true + initContainerImage: docker.io/caapim/graphman-static-init:1.0.4 + cluster: + hostname: gateway.brcmlabs.com + service: + type: ClusterIP + ports: + - name: https + port: 8443 + targetPort: 8443 + protocol: TCP + - name: management + port: 9443 + targetPort: 9443 + protocol: TCP +``` + +## Key Configuration Fields + +### OTK-Specific Fields + +| Field | Description | Required | Default | +|-------|-------------|----------|---------| +| `otk.enabled` | Enable OTK installation | Yes | `false` | +| `otk.type` | OTK type: `dmz`, `internal`, or `single` | Yes | - | +| `otk.initContainerImage` | OTK init container image | Yes | - | +| `otk.dmzKeySecret` | Reference to TLS secret containing DMZ key/cert | Yes (DMZ) | - | +| `otk.internalKeySecret` | Reference to TLS secret containing Internal key/cert | Yes (Internal) | - | +| `otk.dmzAuthSecret` | Reference to secret with DMZ gateway credentials | Yes (Internal) | - | +| `otk.internalAuthSecret` | Reference to secret with Internal gateway credentials | Yes (DMZ) | - | +| `otk.dmzGatewayReference` | Reference to DMZ gateway (name or hostname) | Yes (Internal) | - | +| `otk.internalGatewayReference` | Reference to Internal gateway (name or hostname) | Yes (DMZ) | - | +| `otk.dmzGatewayPort` | Port for DMZ gateway (when external) | No | `9443` or `graphmanDynamicSync` port | +| `otk.internalGatewayPort` | Port for Internal gateway (when external) | No | `9443` or `graphmanDynamicSync` port | +| `otk.syncIntervalSeconds` | Certificate sync interval in seconds | No | `RuntimeSyncIntervalSeconds` or `10` | +| `otk.port` | OTK port (defaults to 8443) | No | `8443` | + +### External Keys Configuration + +Both gateways must have `externalKeys` configured with the `otk: true` flag: + +```yaml +externalKeys: +- name: otk-dmz-tls-secret # or otk-internal-tls-secret + enabled: true + alias: otk-dmz-key # or otk-internal-key + keyUsageType: SSL + otk: true # Required for OTK key handling +``` + +## Deployment + +### 1. Create Secrets + +```bash +cd example/gateway/otk/secrets +./create-secrets.sh default +``` + +### 2. Deploy DMZ Gateway + +```bash +kubectl apply -f example/gateway/otk/otk-ssg-dmz.yaml +``` + +### 3. Deploy Internal Gateway + +```bash +kubectl apply -f example/gateway/otk/otk-ssg-internal.yaml +``` + +### 4. Verify Deployment + +```bash +# Check gateway pods +kubectl get pods -l app=gateway + +# Check gateway status +kubectl get gateway otk-ssg-dmz +kubectl get gateway otk-ssg-internal + +# Check logs +kubectl logs -l app=gateway,gateway-name=otk-ssg-dmz +kubectl logs -l app=gateway,gateway-name=otk-ssg-internal +``` + +## Certificate Synchronization + +The operator automatically synchronizes certificates between DMZ and Internal gateways: + +1. **DMZ Certificate → Internal Gateway**: When the DMZ certificate is updated, it's automatically published to the Internal gateway as a trusted certificate and used for FIP (Federated Identity Provider) user creation. + +2. **Internal Certificate → DMZ Gateway**: When the Internal certificate is updated, it's automatically published to the DMZ gateway as a trusted certificate. + +3. **Sync Interval**: Controlled by `syncIntervalSeconds` (default: 10 seconds or `RuntimeSyncIntervalSeconds`). + +4. **Key Updates**: When DMZ or Internal keys are updated: + - The key is synchronized to the respective gateway + - The DMZ private key name is updated in the cluster-wide property `otk.dmz.private_key.name` (DMZ gateway only) + - Old certificates are removed before new ones are published + +## External Gateway Support + +The operator supports scenarios where one or both gateways are external (not managed by the operator): + +### External DMZ Gateway + +If the DMZ gateway is external, configure the Internal gateway with: + +```yaml +otk: + type: internal + dmzGatewayReference: external-dmz-gateway.example.com + dmzGatewayPort: 9443 # Port for Graphman API + dmzAuthSecret: otk-dmz-auth-secret +``` + +### External Internal Gateway + +If the Internal gateway is external, configure the DMZ gateway with: + +```yaml +otk: + type: dmz + internalGatewayReference: external-internal-gateway.example.com + internalGatewayPort: 9443 # Port for Graphman API + internalAuthSecret: otk-internal-auth-secret +``` + +### External Gateway Requirements + +- Graphman API must be enabled and accessible +- Authentication credentials must be provided via auth secrets +- The correct port must be specified if different from default (9443) +- The gateway must be reachable from the operator's network + +## Troubleshooting + +### Common Issues + +1. **Certificates not synchronizing** + - Verify `syncIntervalSeconds` is set appropriately + - Check that Graphman is enabled on both gateways + - Verify auth secrets are correctly configured + - Check operator logs for errors + +2. **Gateway communication failures** + - Verify gateway references are correct (name or hostname) + - Check network connectivity between gateways + - Verify ports are correctly configured + - Ensure auth secrets contain valid credentials + +3. **Key update failures** + - Verify TLS secrets are of type `kubernetes.io/tls` + - Check that secrets contain both `tls.crt` and `tls.key` + - Ensure `externalKeys` have `otk: true` flag + - Verify alias matches the expected value + +4. **Database connection issues** + - Verify database credentials in `otk.database.auth` + - Check JDBC URL is correct and accessible + - Ensure database exists or `create: true` is set + - Verify database wait timeout is sufficient + +### Checking Certificate Sync Status + +```bash +# Check annotations for certificate thumbprints +kubectl get gateway otk-ssg-dmz -o jsonpath='{.metadata.annotations}' +kubectl get gateway otk-ssg-internal -o jsonpath='{.metadata.annotations}' + +# Check cluster-wide properties +kubectl exec -it -- /opt/SecureSpan/Gateway/node/default/bin/ssgconfig \ + get cluster-wide-properties | grep otk.dmz.private_key.name +``` + +### Operator Logs + +```bash +# View operator logs +kubectl logs -n -l control-plane=controller-manager + +# Filter for OTK-related logs +kubectl logs -n -l control-plane=controller-manager | grep -i otk +``` + +## Additional Resources + +- [Layer7 Gateway Operator Documentation](https://github.com/broadcom/layer7-operator) +- [OAuth Toolkit Documentation](https://techdocs.broadcom.com/us/en/ca-enterprise-software/layer7-api-management/api-gateway/11-1.html) +- Example configurations: `example/gateway/otk/` + +--- + +**Note**: This configuration guide assumes you have a working Kubernetes cluster with the Layer7 Gateway Operator installed. Adjust namespaces, hostnames, and other values according to your environment. + diff --git a/internal/controller/gateway/controller.go b/internal/controller/gateway/controller.go index 92ebd542..a23c13b8 100644 --- a/internal/controller/gateway/controller.go +++ b/internal/controller/gateway/controller.go @@ -111,12 +111,12 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } if gw.Spec.App.Otk.Enabled { - // if gw.Spec.App.Otk.Type == securityv1.OtkTypeDMZ || gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal { - // ops = append(ops, ReconcileOperations{reconcile.ScheduledJobs, "scheduled jobs"}) - // } if gw.Spec.App.Otk.Type == securityv1.OtkTypeSingle && !gw.Spec.App.Management.Database.Enabled { ops = append(ops, ReconcileOperations{reconcile.OTKDatabaseMaintenanceTasks, "otk-db-maintenance-tasks"}) } + if gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal && len(gw.Spec.App.Otk.FipsCertificates) > 0 { + ops = append(ops, ReconcileOperations{reconcile.OtkFipsCerts, "otk-fips-certs"}) + } } params := reconcile.Params{ diff --git a/pkg/gateway/deployment.go b/pkg/gateway/deployment.go index d16fd90a..4a4709ba 100644 --- a/pkg/gateway/deployment.go +++ b/pkg/gateway/deployment.go @@ -826,6 +826,25 @@ func NewDeployment(gw *securityv1.Gateway, platform string) *appsv1.Deployment { EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }) + + if OtkOverrideBundleRequired(gw) { + otkOverrideBundleName := gw.Name + "-otk-override-bundle" + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: otkOverrideBundleName, + MountPath: "/opt/SecureSpan/Gateway/node/default/etc/bootstrap/bundle/001OTK", + }) + volumes = append(volumes, corev1.Volume{ + Name: otkOverrideBundleName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: otkOverrideBundleName, + DefaultMode: &defaultMode, + Optional: &optional, + }, + }, + }) + } + if gw.Spec.App.Otk.Database.Type == securityv1.OtkDatabaseTypeMySQL || gw.Spec.App.Otk.Database.Type == securityv1.OtkDatabaseTypeOracle { if gw.Spec.App.Otk.Database.Create { otkDbInitContainer = true @@ -834,37 +853,40 @@ func NewDeployment(gw *securityv1.Gateway, platform string) *appsv1.Deployment { } if otkInstallInitContainer { - initContainers = append(initContainers, corev1.Container{ - Name: "otk-install-init", - Image: otkInitContainerImage, - ImagePullPolicy: otkInitContainerImagePullPolicy, - SecurityContext: &otkInitContainerSecurityContext, - VolumeMounts: otkInitContainerVolumeMounts, - EnvFrom: []corev1.EnvFromSource{ - { - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: gw.Name + "-otk-shared-init-config", - }, + otkInstallEnvFrom := []corev1.EnvFromSource{ + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: gw.Name + "-otk-shared-init-config", }, }, - { - ConfigMapRef: &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: gw.Name + "-otk-install-init-config", - }, - Optional: &optional, + }, + { + ConfigMapRef: &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: gw.Name + "-otk-install-init-config", }, + Optional: &optional, }, - { - SecretRef: &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: otkInitContainerSecret, - }, - Optional: &optional, + }, + } + if gw.Spec.App.Otk.Type != securityv1.OtkTypeDMZ { + otkInstallEnvFrom = append(otkInstallEnvFrom, corev1.EnvFromSource{ + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: otkInitContainerSecret, }, + Optional: &optional, }, - }, + }) + } + initContainers = append(initContainers, corev1.Container{ + Name: "otk-install-init", + Image: otkInitContainerImage, + ImagePullPolicy: otkInitContainerImagePullPolicy, + SecurityContext: &otkInitContainerSecurityContext, + VolumeMounts: otkInitContainerVolumeMounts, + EnvFrom: otkInstallEnvFrom, TerminationMessagePath: corev1.TerminationMessagePathDefault, TerminationMessagePolicy: corev1.TerminationMessageReadFile, }) diff --git a/pkg/gateway/reconcile/deployment.go b/pkg/gateway/reconcile/deployment.go index eb72bc03..bd1166e5 100644 --- a/pkg/gateway/reconcile/deployment.go +++ b/pkg/gateway/reconcile/deployment.go @@ -357,6 +357,18 @@ func setLabels(ctx context.Context, params Params, dep *appsv1.Deployment) (*app } } } + + if gateway.OtkOverrideBundleRequired(params.Instance) { + otkOverrideSecretName := params.Instance.Name + "-otk-override-bundle" + secret := corev1.Secret{} + err := params.Client.Get(ctx, types.NamespacedName{Name: otkOverrideSecretName, Namespace: params.Instance.Namespace}, &secret) + if err == nil { + if checksum, ok := secret.ObjectMeta.Annotations["checksum/data"]; ok { + dep.ObjectMeta.Labels[otkOverrideSecretName+"-checksum"] = checksum + } + } + } + commits := "" for _, repoRef := range params.Instance.Spec.App.RepositoryReferences { for _, repoStatus := range params.Instance.Status.RepositoryStatus { diff --git a/pkg/gateway/reconcile/gateway.go b/pkg/gateway/reconcile/gateway.go index 0dc931fe..28b9a6e2 100644 --- a/pkg/gateway/reconcile/gateway.go +++ b/pkg/gateway/reconcile/gateway.go @@ -698,6 +698,107 @@ func NewGwUpdateRequest(ctx context.Context, gateway *securityv1.Gateway, params gwUpdReq.graphmanEncryptionPassphrase = "" gwUpdReq.bundleName = string(gwUpdReq.bundleType) gwUpdReq.externalEntities = externalSecrets + case BundleTypeOTKFips: + otkFipsCerts := []ExternalEntity{} + + for k, v := range gateway.Status.LastAppliedOtkFipsCerts { + found := false + for _, fc := range gateway.Spec.App.Otk.FipsCertificates { + if k == fc.Name { + found = true + } + } + if !found { + bundleBytes, err := util.BuildFipUserDeleteBundle(v) + if err != nil { + return nil, err + } + annotation := "security.brcmlabs.com/otk-fips-certs-" + k + otkFipsCerts = append(otkFipsCerts, ExternalEntity{ + Name: k, + Annotation: annotation, + Bundle: bundleBytes, + Checksum: "deleted", + CacheEntry: gateway.Name + "-" + string(gwUpdReq.bundleType) + "-" + k + "-deleted", + }) + } + } + + for _, fipsCert := range gateway.Spec.App.Otk.FipsCertificates { + if !fipsCert.Enabled { + continue + } + + var sha1Sum string + var leafCerts []util.FipUserCert + + switch strings.ToLower(fipsCert.Type) { + case "secret": + secret, err := getGatewaySecret(ctx, params, fipsCert.Name) + if err != nil { + return nil, err + } + leafCerts = util.ExtractLeafCertsFromSecret(secret) + dataBytes, _ := json.Marshal(&secret.Data) + h := sha1.New() + h.Write(dataBytes) + sha1Sum = fmt.Sprintf("%x", h.Sum(nil)) + case "configmap": + cm, err := getGatewayConfigMap(ctx, params, fipsCert.Name) + if err != nil { + return nil, err + } + leafCerts = util.ExtractLeafCertsFromConfigMap(cm) + dataBytes, _ := json.Marshal(&cm.Data) + h := sha1.New() + h.Write(dataBytes) + sha1Sum = fmt.Sprintf("%x", h.Sum(nil)) + default: + params.Log.V(2).Info("unsupported fips certificate source type", "type", fipsCert.Type, "name", fipsCert.Name) + continue + } + + notFound := []string{} + if gateway.Status.LastAppliedOtkFipsCerts != nil && gateway.Status.LastAppliedOtkFipsCerts[fipsCert.Name] != nil { + for _, appliedCert := range gateway.Status.LastAppliedOtkFipsCerts[fipsCert.Name] { + found := false + for _, desired := range leafCerts { + if appliedCert == desired.SubjectDn { + found = true + } + } + if !found { + notFound = append(notFound, appliedCert) + } + } + } + + if len(leafCerts) < 1 && len(notFound) < 1 { + continue + } + + bundleBytes, err := util.BuildFipUserBundle(leafCerts, notFound) + if err != nil { + return nil, err + } + + if sha1Sum == "" { + sha1Sum = "deleted" + } + + annotation := "security.brcmlabs.com/otk-fips-certs-" + fipsCert.Name + otkFipsCerts = append(otkFipsCerts, ExternalEntity{ + Name: fipsCert.Name, + Annotation: annotation, + Bundle: bundleBytes, + Checksum: sha1Sum, + CacheEntry: gateway.Name + "-" + string(gwUpdReq.bundleType) + "-" + fipsCert.Name + "-" + sha1Sum, + }) + } + + gwUpdReq.graphmanEncryptionPassphrase = "" + gwUpdReq.bundleName = string(gwUpdReq.bundleType) + gwUpdReq.externalEntities = otkFipsCerts } return gwUpdReq, nil @@ -2612,6 +2713,48 @@ func updateEntityStatus(ctx context.Context, kind string, name string, bundleByt if err := params.Client.Status().Update(ctx, params.Instance); err != nil { return fmt.Errorf("failed to update external cert status: %w", err) } + case "otk fips": + bundle := graphman.Bundle{} + err := json.Unmarshal(bundleBytes, &bundle) + if err != nil { + return err + } + fipUsers := []string{} + if params.Instance.Status.LastAppliedOtkFipsCerts == nil { + for _, fipUser := range bundle.FipUsers { + fipUsers = append(fipUsers, fipUser.SubjectDn) + } + } else { + for _, appliedFipUser := range bundle.FipUsers { + found := false + if bundle.Properties != nil { + for _, mapping := range bundle.Properties.Mappings.FipUsers { + mappingSource := MappingSource{} + sourceBytes, err := json.Marshal(mapping.Source) + if err != nil { + return err + } + err = json.Unmarshal(sourceBytes, &mappingSource) + if err != nil { + return err + } + if appliedFipUser.SubjectDn == mappingSource.Name && mapping.Action == graphman.MappingActionDelete { + found = true + } + } + } + if !found { + fipUsers = append(fipUsers, appliedFipUser.SubjectDn) + } + } + } + if params.Instance.Status.LastAppliedOtkFipsCerts == nil { + params.Instance.Status.LastAppliedOtkFipsCerts = map[string][]string{} + } + params.Instance.Status.LastAppliedOtkFipsCerts[name] = fipUsers + if err := params.Client.Status().Update(ctx, params.Instance); err != nil { + return fmt.Errorf("failed to update otk fips cert status: %w", err) + } } return nil diff --git a/pkg/gateway/reconcile/otkfipscerts.go b/pkg/gateway/reconcile/otkfipscerts.go new file mode 100644 index 00000000..99aa7f42 --- /dev/null +++ b/pkg/gateway/reconcile/otkfipscerts.go @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2025 Broadcom. All rights reserved. +* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +* All trademarks, trade names, service marks, and logos referenced +* herein belong to their respective companies. +* +* This software and all information contained therein is confidential +* and proprietary and shall not be duplicated, used, disclosed or +* disseminated in any way except as authorized by the applicable +* license agreement, without the express written permission of Broadcom. +* All authorized reproductions must be marked with this language. +* +* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE +* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS +* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION +* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, +* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO +* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR +* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, +* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR +* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE +* POSSIBILITY OF SUCH LOSS OR DAMAGE. +* +* AI assistance has been used to generate some or all contents of this file. That includes, but is not limited to, new code, modifying existing code, stylistic edits. + */ +package reconcile + +import ( + "context" +) + +func OtkFipsCerts(ctx context.Context, params Params) error { + gateway := params.Instance + if len(gateway.Spec.App.Otk.FipsCertificates) == 0 { + if len(gateway.Status.LastAppliedOtkFipsCerts) == 0 { + return nil + } + } + + gwUpdReq, err := NewGwUpdateRequest( + ctx, + gateway, + params, + WithBundleType(BundleTypeOTKFips), + ) + + if err != nil { + return err + } + + if gwUpdReq == nil { + return nil + } + + for _, fipsCert := range gwUpdReq.externalEntities { + fipsCertUpdReq := gwUpdReq + fipsCertUpdReq.bundle = fipsCert.Bundle + fipsCertUpdReq.bundleName = fipsCert.Name + fipsCertUpdReq.checksum = fipsCert.Checksum + fipsCertUpdReq.cacheEntry = fipsCert.CacheEntry + fipsCertUpdReq.patchAnnotation = fipsCert.Annotation + err = SyncGateway(ctx, params, *fipsCertUpdReq) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/gateway/reconcile/secret.go b/pkg/gateway/reconcile/secret.go index 14849c3e..abfe989c 100644 --- a/pkg/gateway/reconcile/secret.go +++ b/pkg/gateway/reconcile/secret.go @@ -1,3 +1,29 @@ +/* +* Copyright (c) 2025 Broadcom. All rights reserved. +* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +* All trademarks, trade names, service marks, and logos referenced +* herein belong to their respective companies. +* +* This software and all information contained therein is confidential +* and proprietary and shall not be duplicated, used, disclosed or +* disseminated in any way except as authorized by the applicable +* license agreement, without the express written permission of Broadcom. +* All authorized reproductions must be marked with this language. +* +* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE +* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS +* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION +* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, +* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO +* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR +* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, +* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR +* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE +* POSSIBILITY OF SUCH LOSS OR DAMAGE. +* +* AI assistance has been used to generate some or all contents of this file. That includes, but is not limited to, new code, modifying existing code, stylistic edits. + */ package reconcile import ( @@ -45,6 +71,16 @@ func Secrets(ctx context.Context, params Params) error { desiredSecrets = append(desiredSecrets, desiredSecret) } + if gateway.OtkOverrideBundleRequired(params.Instance) { + desiredSecret, err := gateway.NewOtkOverrideBundleSecret(params.Instance) + if err != nil { + return err + } + if desiredSecret != nil { + desiredSecrets = append(desiredSecrets, desiredSecret) + } + } + if err := reconcileSecrets(ctx, params, desiredSecrets); err != nil { return fmt.Errorf("failed to reconcile secrets: %w", err) } diff --git a/pkg/gateway/secrets.go b/pkg/gateway/secrets.go index 9d4fb811..81d5f6ff 100644 --- a/pkg/gateway/secrets.go +++ b/pkg/gateway/secrets.go @@ -1,3 +1,29 @@ +/* +* Copyright (c) 2025 Broadcom. All rights reserved. +* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +* All trademarks, trade names, service marks, and logos referenced +* herein belong to their respective companies. +* +* This software and all information contained therein is confidential +* and proprietary and shall not be duplicated, used, disclosed or +* disseminated in any way except as authorized by the applicable +* license agreement, without the express written permission of Broadcom. +* All authorized reproductions must be marked with this language. +* +* EXCEPT AS SET FORTH IN THE APPLICABLE LICENSE AGREEMENT, TO THE +* EXTENT PERMITTED BY APPLICABLE LAW OR AS AGREED BY BROADCOM IN ITS +* APPLICABLE LICENSE AGREEMENT, BROADCOM PROVIDES THIS DOCUMENTATION +* "AS IS" WITHOUT WARRANTY OF ANY KIND, INCLUDING WITHOUT LIMITATION, +* ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +* PURPOSE, OR. NONINFRINGEMENT. IN NO EVENT WILL BROADCOM BE LIABLE TO +* THE END USER OR ANY THIRD PARTY FOR ANY LOSS OR DAMAGE, DIRECT OR +* INDIRECT, FROM THE USE OF THIS DOCUMENTATION, INCLUDING WITHOUT LIMITATION, +* LOST PROFITS, LOST INVESTMENT, BUSINESS INTERRUPTION, GOODWILL, OR +* LOST DATA, EVEN IF BROADCOM IS EXPRESSLY ADVISED IN ADVANCE OF THE +* POSSIBILITY OF SUCH LOSS OR DAMAGE. +* +* AI assistance has been used to generate some or all contents of this file. That includes, but is not limited to, new code, modifying existing code, stylistic edits. + */ package gateway import ( @@ -5,6 +31,7 @@ import ( "encoding/json" "fmt" "regexp" + "strings" securityv1 "github.com/caapim/layer7-operator/api/v1" "github.com/caapim/layer7-operator/pkg/util" @@ -253,6 +280,85 @@ func NewSecret(gw *securityv1.Gateway, name string) (*corev1.Secret, error) { return secret, nil } +func OtkOverrideBundleRequired(gw *securityv1.Gateway) bool { + if !gw.Spec.App.Otk.Enabled { + return false + } + if gw.Spec.App.Otk.Type == securityv1.OtkTypeInternal || gw.Spec.App.Otk.Type == securityv1.OtkTypeDMZ { + return true + } + if gw.Spec.App.ClusterProperties.Enabled { + for _, p := range gw.Spec.App.ClusterProperties.Properties { + if p.Name == "otk.port" { + return false + } + } + } + return true +} + +func NewOtkOverrideBundleSecret(gw *securityv1.Gateway) (*corev1.Secret, error) { + if !OtkOverrideBundleRequired(gw) { + return nil, nil + } + + otkPort := 8443 + if gw.Spec.App.Otk.OTKPort != 0 { + otkPort = gw.Spec.App.Otk.OTKPort + } + + includeOtkPort := true + if gw.Spec.App.ClusterProperties.Enabled { + for _, p := range gw.Spec.App.ClusterProperties.Properties { + if p.Name == "otk.port" { + includeOtkPort = false + break + } + } + } + + mode := strings.ToUpper(string(gw.Spec.App.Otk.Type)) + gatewayHost := "" + switch gw.Spec.App.Otk.Type { + case securityv1.OtkTypeInternal: + gatewayHost = gw.Spec.App.Otk.DmzOTKGateway.Url + if gw.Spec.App.Otk.DmzOTKGateway.Port != 0 { + otkPort = gw.Spec.App.Otk.DmzOTKGateway.Port + } + case securityv1.OtkTypeDMZ: + gatewayHost = gw.Spec.App.Otk.InternalOTKGateway.Url + if gw.Spec.App.Otk.InternalOTKGateway.Port != 0 { + otkPort = gw.Spec.App.Otk.InternalOTKGateway.Port + } + } + + bundleBytes, checksum, err := util.BuildOtkOverrideBundle(mode, gatewayHost, otkPort, includeOtkPort) + if err != nil { + return nil, err + } + + data := map[string][]byte{ + "otk-override-bundle.json": bundleBytes, + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: gw.Name + "-otk-override-bundle", + Namespace: gw.Namespace, + Labels: util.DefaultLabels(gw.Name, gw.Spec.App.Labels), + Annotations: map[string]string{"checksum/data": checksum}, + }, + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + Type: corev1.SecretTypeOpaque, + Data: data, + } + + return secret, nil +} + func NewOtkCertificateSecret(gw *securityv1.Gateway, name string, data map[string][]byte) *corev1.Secret { dataBytes, _ := json.Marshal(data) h := sha1.New() diff --git a/pkg/util/graphman.go b/pkg/util/graphman.go index 02e0c799..ae807363 100644 --- a/pkg/util/graphman.go +++ b/pkg/util/graphman.go @@ -49,6 +49,7 @@ import ( "time" graphman "github.com/caapim/layer7-operator/internal/graphman" + corev1 "k8s.io/api/core/v1" ) const fipsProviderGuid = "41e5cacd15f86758f03ff2952616d4f3" @@ -567,9 +568,20 @@ func BuildAndValidateBundle(path string, processNestedRepos bool) (bundleBytes [ return bundleBytes, nil } -func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int) ([]byte, string, error) { +func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int, includeOtkPort bool) ([]byte, string, error) { var bundle graphman.Bundle var policyXml []byte + + appendOtkPort := func() { + if includeOtkPort { + bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ + Name: "otk.port", + Value: strconv.Itoa(otkPort), + Description: "OTK Port", + }) + } + } + switch mode { case "INTERNAL": for _, internalPolicy := range internalPolicies { @@ -595,12 +607,8 @@ func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int) ([]byt Soap: false, }) } - bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ - Name: "otk.port", - Value: strconv.Itoa(otkPort), - Description: "OTK Port", - }) } + appendOtkPort() bundle.FederatedIdps = append(bundle.FederatedIdps, &graphman.FederatedIdpInput{ Name: "otk-fips-provider", @@ -634,18 +642,10 @@ func BuildOtkOverrideBundle(mode string, gatewayHost string, otkPort int) ([]byt Soap: false, }) } - bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ - Name: "otk.port", - Value: strconv.Itoa(otkPort), - Description: "OTK Port", - }) } + appendOtkPort() case "SINGLE": - bundle.ClusterProperties = append(bundle.ClusterProperties, &graphman.ClusterPropertyInput{ - Name: "otk.port", - Value: strconv.Itoa(otkPort), - Description: "OTK Port", - }) + appendOtkPort() default: return nil, "", fmt.Errorf("invalid otk installation type %s. Valid types are single, dmz and internal", mode) } @@ -819,3 +819,160 @@ func getSha1Thumbprint(rawCert []byte) (string, error) { // bundle.Keys = append(bundle.Keys, &gmanKey) // } + +type FipUserCert struct { + SubjectDn string + CertBase64 string + Name string +} + +func ExtractLeafCertsFromSecret(secret *corev1.Secret) []FipUserCert { + var certs []FipUserCert + + if secret.Type == corev1.SecretTypeTLS { + if tlsCrt, ok := secret.Data["tls.crt"]; ok { + if cert := extractFirstLeafCert(tlsCrt); cert != nil { + certs = append(certs, *cert) + } + } + return certs + } + + for _, v := range secret.Data { + if !strings.Contains(string(v), "-----BEGIN CERTIFICATE-----") { + continue + } + if cert := extractFirstLeafCert(v); cert != nil { + certs = append(certs, *cert) + } + } + return certs +} + +func ExtractLeafCertsFromConfigMap(cm *corev1.ConfigMap) []FipUserCert { + var certs []FipUserCert + for k, v := range cm.Data { + if !strings.HasSuffix(k, ".crt") { + continue + } + if !strings.Contains(v, "-----BEGIN CERTIFICATE-----") { + continue + } + certs = append(certs, extractAllCerts([]byte(v))...) + } + return certs +} + +func extractFirstLeafCert(pemData []byte) *FipUserCert { + block, _ := pem.Decode(pemData) + if block == nil { + return nil + } + crtX509, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil + } + return &FipUserCert{ + SubjectDn: strings.ToLower(crtX509.Subject.String()), + CertBase64: base64.StdEncoding.EncodeToString(block.Bytes), + Name: crtX509.Subject.CommonName, + } +} + +func extractAllCerts(pemData []byte) []FipUserCert { + var certs []FipUserCert + rest := pemData + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + crtX509, err := x509.ParseCertificate(block.Bytes) + if err != nil { + continue + } + certs = append(certs, FipUserCert{ + SubjectDn: strings.ToLower(crtX509.Subject.String()), + CertBase64: base64.StdEncoding.EncodeToString(block.Bytes), + Name: crtX509.Subject.CommonName, + }) + } + return certs +} + +func cnFromSubjectDn(dn string) string { + upper := strings.ToUpper(dn) + if strings.HasPrefix(upper, "CN=") { + return dn[3:] + } + for _, part := range strings.Split(dn, ",") { + part = strings.TrimSpace(part) + if strings.HasPrefix(strings.ToUpper(part), "CN=") { + return part[3:] + } + } + return dn +} + +func BuildFipUserBundle(certs []FipUserCert, notFound []string) ([]byte, error) { + bundle := graphman.Bundle{} + + for _, cert := range certs { + bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ + Name: cert.Name, + ProviderName: "otk-fips-provider", + SubjectDn: cert.SubjectDn, + CertBase64: cert.CertBase64, + Login: cert.Name, + }) + } + + if len(notFound) > 0 { + bundle.Properties = &graphman.BundleProperties{} + for _, subjectDn := range notFound { + login := cnFromSubjectDn(subjectDn) + bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ + Name: login, + ProviderName: "otk-fips-provider", + SubjectDn: subjectDn, + Login: login, + }) + bundle.Properties.Mappings.FipUsers = append(bundle.Properties.Mappings.FipUsers, &graphman.MappingInstructionInput{ + Action: graphman.MappingActionDelete, + Source: graphman.MappingSource{Name: login}, + }) + } + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return nil, err + } + return bundleBytes, nil +} + +func BuildFipUserDeleteBundle(subjectDns []string) ([]byte, error) { + bundle := graphman.Bundle{} + bundle.Properties = &graphman.BundleProperties{} + + for _, subjectDn := range subjectDns { + login := cnFromSubjectDn(subjectDn) + bundle.FipUsers = append(bundle.FipUsers, &graphman.FipUserInput{ + Name: login, + ProviderName: "otk-fips-provider", + SubjectDn: subjectDn, + Login: login, + }) + bundle.Properties.Mappings.FipUsers = append(bundle.Properties.Mappings.FipUsers, &graphman.MappingInstructionInput{ + Action: graphman.MappingActionDelete, + Source: graphman.MappingSource{Name: login}, + }) + } + + bundleBytes, err := json.Marshal(bundle) + if err != nil { + return nil, err + } + return bundleBytes, nil +}