From 6f2e908b1e332d7c66ee4f44aa0bfe169d1f6551 Mon Sep 17 00:00:00 2001 From: hardikl Date: Tue, 18 Jun 2024 16:26:29 +0530 Subject: [PATCH 01/14] feat: Implementing certificate expiry detail in security dashboard --- conf/rest/9.12.0/certificate.yaml | 24 ++++ conf/rest/default.yaml | 1 + conf/zapi/cdot/9.8.0/certificate.yaml | 27 +++++ conf/zapi/default.yaml | 1 + grafana/dashboards/cmode/security.json | 158 ++++++++++++++++++++++++- 5 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 conf/rest/9.12.0/certificate.yaml create mode 100644 conf/zapi/cdot/9.8.0/certificate.yaml diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml new file mode 100644 index 000000000..a8bda16dc --- /dev/null +++ b/conf/rest/9.12.0/certificate.yaml @@ -0,0 +1,24 @@ + +name: Certificate +query: api/security/certificates +object: certificate + +counters: + - ^^uuid + - ^name + - ^public_certificate => certificatePEM + - ^scope => scope + - ^serial_number => serial_number + - ^svm.name => svm + - ^type => type + - expiry_time(timestamp) => expiry_time + +export_options: + instance_keys: + - name + - svm + - uuid + instance_labels: + - scope + - serial_number + - type diff --git a/conf/rest/default.yaml b/conf/rest/default.yaml index 6676c43b8..afdc7f53c 100644 --- a/conf/rest/default.yaml +++ b/conf/rest/default.yaml @@ -10,6 +10,7 @@ schedule: objects: Aggregate: aggr.yaml + Certificate: certificate.yaml # The CIFSSession template may slow down data collection due to a high number of metrics. # CIFSSession: cifs_session.yaml # CIFSShare: cifs_share.yaml diff --git a/conf/zapi/cdot/9.8.0/certificate.yaml b/conf/zapi/cdot/9.8.0/certificate.yaml new file mode 100644 index 000000000..96524fc4e --- /dev/null +++ b/conf/zapi/cdot/9.8.0/certificate.yaml @@ -0,0 +1,27 @@ + +name: Certificate +query: security-certificate-get-iter +object: certificate + +counters: + certificate-info: + - ^^cert-name => name + - ^^serial-number => serial_number + - ^^vserver => svm + - ^public-certificate => certificatePEM + - ^type => type + - expiration-date => expiry_time + +plugins: + - LabelAgent: + join: + - uuid `_` name,serial_number,svm + +export_options: + instance_keys: + - name + - svm + - uuid + instance_labels: + - serial_number + - type diff --git a/conf/zapi/default.yaml b/conf/zapi/default.yaml index 86e87dd7d..131900b1e 100644 --- a/conf/zapi/default.yaml +++ b/conf/zapi/default.yaml @@ -8,6 +8,7 @@ schedule: objects: Aggregate: aggr.yaml AggregateEfficiency: aggr_efficiency.yaml + Certificate: certificate.yaml # The CIFSSession template may slow down data collection due to a high number of metrics. # CIFSSession: cifs_session.yaml # CIFSShare: cifs_share.yaml diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 6071e1036..67e187719 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1900,6 +1900,158 @@ ], "type": "stat" }, + { + "datasource": "${DS_PROMETHEUS}", + "description": "This panel displays Certificate expiration time.", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "transparent", + "mode": "fixed" + }, + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Expire" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeFromNow" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 228, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Value #B" + } + ] + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": "${DS_PROMETHEUS}", + "editorMode": "code", + "exemplar": false, + "expr": "certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", svm=~\"$SVM\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "datasource": "${DS_PROMETHEUS}", + "editorMode": "code", + "exemplar": false, + "expr": "certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", svm=~\"$SVM\"} * 1000", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Certificates Detail", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "uuid", + "mode": "outer" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "cluster", + "name", + "scope", + "svm", + "type", + "Value #B" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "cluster 2": true, + "datacenter 2": true, + "isEncrypted": true, + "isHardwareEncrypted": true, + "name 2": true, + "svm 2": true + }, + "includeByName": {}, + "indexByName": { + "cluster": 0, + "name": 2, + "scope": 3, + "svm": 1, + "type": 4 + }, + "renameByName": { + "Value #B": "Expire", + "cluster": "Cluster", + "name": "Certificate Name", + "scope": "Scope", + "svm": "SVM", + "type": "Type" + } + } + } + ], + "type": "table" + }, { "collapsed": true, "datasource": "${DS_PROMETHEUS}", @@ -1907,7 +2059,7 @@ "h": 1, "w": 24, "x": 0, - "y": 26 + "y": 33 }, "id": 12, "panels": [ @@ -2253,7 +2405,7 @@ "h": 1, "w": 24, "x": 0, - "y": 29 + "y": 36 }, "id": 223, "panels": [ @@ -3830,7 +3982,7 @@ "h": 1, "w": 24, "x": 0, - "y": 30 + "y": 37 }, "id": 227, "panels": [ From 26fab7c685801000abe20559705e159e80818ab4 Mon Sep 17 00:00:00 2001 From: hardikl Date: Wed, 19 Jun 2024 16:53:05 +0530 Subject: [PATCH 02/14] feat: Handling review comments- REST only feature --- .../rest/plugins/certificate/certificate.go | 226 ++--------------- .../securitycertificate.go | 233 ++++++++++++++++++ cmd/collectors/rest/rest.go | 7 +- cmd/collectors/zapi/collector/zapi.go | 6 +- .../securitycertificate.go} | 18 +- conf/rest/9.12.0/certificate.yaml | 22 +- conf/rest/9.12.0/security_certificate.yaml | 2 +- conf/zapi/cdot/9.8.0/certificate.yaml | 27 -- .../zapi/cdot/9.8.0/security_certificate.yaml | 2 +- conf/zapi/default.yaml | 1 - grafana/dashboards/cmode/security.json | 53 ++-- 11 files changed, 301 insertions(+), 296 deletions(-) create mode 100644 cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go rename cmd/collectors/zapi/plugins/{certificate/certificate.go => securitycertificate/securitycertificate.go} (91%) delete mode 100644 conf/zapi/cdot/9.8.0/certificate.yaml diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 3325b801c..9dbca8bda 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -1,233 +1,45 @@ -/* - * Copyright NetApp Inc, 2022 All rights reserved - */ - package certificate import ( - "crypto/x509" - "encoding/pem" - "github.com/netapp/harvest/v2/cmd/collectors" "github.com/netapp/harvest/v2/cmd/poller/plugin" - "github.com/netapp/harvest/v2/cmd/tools/rest" - "github.com/netapp/harvest/v2/pkg/conf" - ontap "github.com/netapp/harvest/v2/pkg/errs" "github.com/netapp/harvest/v2/pkg/matrix" "github.com/netapp/harvest/v2/pkg/util" - "github.com/tidwall/gjson" - "time" + "strings" ) type Certificate struct { *plugin.AbstractPlugin - currentVal int - client *rest.Client } func New(p *plugin.AbstractPlugin) plugin.Plugin { return &Certificate{AbstractPlugin: p} } -func (my *Certificate) Init() error { - - var err error - - if err := my.InitAbc(); err != nil { - return err - } - - timeout, _ := time.ParseDuration(rest.DefaultTimeout) - if my.client, err = rest.New(conf.ZapiPoller(my.ParentParams), timeout, my.Auth); err != nil { - my.Logger.Error().Stack().Err(err).Msg("connecting") - return err - } - - if err := my.client.Init(5); err != nil { - return err - } - - // Assigned the value to currentVal so that plugin would be invoked first time to populate cache. - my.currentVal = my.SetPluginInterval() - - return nil -} - func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { var ( - adminVserver string - adminVserverSerial string - err error + dateTimeVal string + gmt string ) data := dataMap[my.Object] - my.client.Metadata.Reset() - - if my.currentVal >= my.PluginInvocationRate { - my.currentVal = 0 - // invoke private vserver cli rest and get admin vserver name - if adminVserver, err = my.GetAdminVserver(); err != nil { - if ontap.IsRestErr(err, ontap.APINotFound) { - my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM") - } else { - my.Logger.Error().Err(err).Msg("Failed to collect admin SVM") - } - return nil, nil, nil + // update certificate instance based on admin vaserver serial + for _, certificateInstance := range data.GetInstances() { + expiryTime := certificateInstance.GetLabel("expiry_time") + // parsing 2027-08-10T23:33:22+12:00 and converting to Date:2027-08-10 23:33:22 GMT:+12:00 + fields := strings.Split(expiryTime, "T") + if strings.Contains(fields[1], "+") { + f := strings.Split(fields[1], "+") + dateTimeVal = fields[0] + " " + f[0] + gmt = "+" + f[1] + } else if strings.Contains(fields[1], "-") { + f := strings.Split(fields[1], "-") + dateTimeVal = fields[0] + " " + f[0] + gmt = "-" + f[1] } - - // invoke private ssl cli rest and get the admin SVM's serial number - if adminVserverSerial, err = my.GetSecuritySsl(adminVserver); err != nil { - if ontap.IsRestErr(err, ontap.APINotFound) { - my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM's serial number") - } else { - my.Logger.Error().Msg("Failed to collect admin SVM's serial number") - } - return nil, nil, nil - } - - // update certificate instance based on admin vaserver serial - for _, certificateInstance := range data.GetInstances() { - if certificateInstance.IsExportable() { - certificateInstance.SetExportable(false) - serialNumber := certificateInstance.GetLabel("serial_number") - - if serialNumber == adminVserverSerial { - certificateInstance.SetExportable(true) - // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity - certificateInstance.SetLabel("svm", adminVserver) - my.setCertificateIssuerType(certificateInstance) - my.setCertificateValidity(data, certificateInstance) - } - } - } - - } - - my.currentVal++ - return nil, my.client.Metadata, nil -} - -func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance) { - var ( - cert *x509.Certificate - err error - ) - - certificatePEM := instance.GetLabel("certificatePEM") - certUUID := instance.GetLabel("uuid") - - if certificatePEM == "" { - my.Logger.Debug().Str("uuid", certUUID).Msg("Certificate is not found") - instance.SetLabel("certificateIssuerType", "unknown") - } else { - instance.SetLabel("certificateIssuerType", "self_signed") - certDecoded, _ := pem.Decode([]byte(certificatePEM)) - if certDecoded == nil { - my.Logger.Warn().Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") - instance.SetLabel("certificateIssuerType", "unknown") - return - } - - if cert, err = x509.ParseCertificate(certDecoded.Bytes); err != nil { - my.Logger.Warn().Err(err).Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") - instance.SetLabel("certificateIssuerType", "unknown") - return - } - - // Verifies if certificate is self-issued. This is true if the subject and issuer are equal. - if cert.Subject.String() == cert.Issuer.String() { - // Verifies if certificate is self-signed. This is true if the certificate is signed using its own public key. - if err = cert.CheckSignature(x509.SHA256WithRSA, cert.RawTBSCertificate, cert.Signature); err != nil { - // Any verification exception means it is not signed with the give key. i.e. not self-signed - instance.SetLabel("certificateIssuerType", "ca_signed") - } - } else { - instance.SetLabel("certificateIssuerType", "ca_signed") - } - } -} - -func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { - var ( - expiryTimeMetric *matrix.Metric - ) - - instance.SetLabel("certificateExpiryStatus", "unknown") - - if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { - my.Logger.Error().Stack().Msg("missing expiry time metric") - return - } - - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { - // convert expiryTime from float64 to int64 and find difference - - timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() - - if timestampDiff <= 0 { - instance.SetLabel("certificateExpiryStatus", "expired") - } else { - // daysRemaining will be more than 0 if it has reached this point, convert to days - daysRemaining := timestampDiff / 24 - if daysRemaining < 60 { - instance.SetLabel("certificateExpiryStatus", "expiring") - } else { - instance.SetLabel("certificateExpiryStatus", "active") - } - } - } - -} - -func (my *Certificate) GetAdminVserver() (string, error) { - - var ( - result []gjson.Result - err error - adminVserver string - ) - - query := "api/private/cli/vserver" - href := rest.NewHrefBuilder(). - APIPath(query). - Fields([]string{"type"}). - Filter([]string{"type=admin"}). - Build() - - if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { - return "", err - } - - // This should be one iteration only as cluster can have one admin vserver - for _, svm := range result { - adminVserver = svm.Get("vserver").String() - } - return adminVserver, nil -} - -func (my *Certificate) GetSecuritySsl(adminSvm string) (string, error) { - - var ( - result []gjson.Result - err error - adminSerial string - ) - - query := "api/private/cli/security/ssl" - href := rest.NewHrefBuilder(). - APIPath(query). - Fields([]string{"serial"}). - Filter([]string{"vserver=" + adminSvm}). - Build() - - if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { - return "", err - } - - // This should be one iteration only as cluster can have one admin vserver - for _, ssl := range result { - adminSerial = ssl.Get("serial").String() + certificateInstance.SetLabel("dateTime", dateTimeVal) + certificateInstance.SetLabel("gmt", gmt) } - return adminSerial, nil + return nil, nil, nil } diff --git a/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go b/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go new file mode 100644 index 000000000..d558c7786 --- /dev/null +++ b/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go @@ -0,0 +1,233 @@ +/* + * Copyright NetApp Inc, 2022 All rights reserved + */ + +package securitycertificate + +import ( + "crypto/x509" + "encoding/pem" + "github.com/netapp/harvest/v2/cmd/collectors" + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/cmd/tools/rest" + "github.com/netapp/harvest/v2/pkg/conf" + ontap "github.com/netapp/harvest/v2/pkg/errs" + "github.com/netapp/harvest/v2/pkg/matrix" + "github.com/netapp/harvest/v2/pkg/util" + "github.com/tidwall/gjson" + "time" +) + +type SecurityCertificate struct { + *plugin.AbstractPlugin + currentVal int + client *rest.Client +} + +func New(p *plugin.AbstractPlugin) plugin.Plugin { + return &SecurityCertificate{AbstractPlugin: p} +} + +func (my *SecurityCertificate) Init() error { + + var err error + + if err := my.InitAbc(); err != nil { + return err + } + + timeout, _ := time.ParseDuration(rest.DefaultTimeout) + if my.client, err = rest.New(conf.ZapiPoller(my.ParentParams), timeout, my.Auth); err != nil { + my.Logger.Error().Stack().Err(err).Msg("connecting") + return err + } + + if err := my.client.Init(5); err != nil { + return err + } + + // Assigned the value to currentVal so that plugin would be invoked first time to populate cache. + my.currentVal = my.SetPluginInterval() + + return nil +} + +func (my *SecurityCertificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { + + var ( + adminVserver string + adminVserverSerial string + err error + ) + data := dataMap[my.Object] + my.client.Metadata.Reset() + + if my.currentVal >= my.PluginInvocationRate { + my.currentVal = 0 + + // invoke private vserver cli rest and get admin vserver name + if adminVserver, err = my.GetAdminVserver(); err != nil { + if ontap.IsRestErr(err, ontap.APINotFound) { + my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM") + } else { + my.Logger.Error().Err(err).Msg("Failed to collect admin SVM") + } + return nil, nil, nil + } + + // invoke private ssl cli rest and get the admin SVM's serial number + if adminVserverSerial, err = my.GetSecuritySsl(adminVserver); err != nil { + if ontap.IsRestErr(err, ontap.APINotFound) { + my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM's serial number") + } else { + my.Logger.Error().Msg("Failed to collect admin SVM's serial number") + } + return nil, nil, nil + } + + // update certificate instance based on admin vaserver serial + for _, certificateInstance := range data.GetInstances() { + if certificateInstance.IsExportable() { + certificateInstance.SetExportable(false) + serialNumber := certificateInstance.GetLabel("serial_number") + + if serialNumber == adminVserverSerial { + certificateInstance.SetExportable(true) + // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity + certificateInstance.SetLabel("svm", adminVserver) + my.setCertificateIssuerType(certificateInstance) + my.setCertificateValidity(data, certificateInstance) + } + } + } + + } + + my.currentVal++ + return nil, my.client.Metadata, nil +} + +func (my *SecurityCertificate) setCertificateIssuerType(instance *matrix.Instance) { + var ( + cert *x509.Certificate + err error + ) + + certificatePEM := instance.GetLabel("certificatePEM") + certUUID := instance.GetLabel("uuid") + + if certificatePEM == "" { + my.Logger.Debug().Str("uuid", certUUID).Msg("Certificate is not found") + instance.SetLabel("certificateIssuerType", "unknown") + } else { + instance.SetLabel("certificateIssuerType", "self_signed") + certDecoded, _ := pem.Decode([]byte(certificatePEM)) + if certDecoded == nil { + my.Logger.Warn().Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") + instance.SetLabel("certificateIssuerType", "unknown") + return + } + + if cert, err = x509.ParseCertificate(certDecoded.Bytes); err != nil { + my.Logger.Warn().Err(err).Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") + instance.SetLabel("certificateIssuerType", "unknown") + return + } + + // Verifies if certificate is self-issued. This is true if the subject and issuer are equal. + if cert.Subject.String() == cert.Issuer.String() { + // Verifies if certificate is self-signed. This is true if the certificate is signed using its own public key. + if err = cert.CheckSignature(x509.SHA256WithRSA, cert.RawTBSCertificate, cert.Signature); err != nil { + // Any verification exception means it is not signed with the give key. i.e. not self-signed + instance.SetLabel("certificateIssuerType", "ca_signed") + } + } else { + instance.SetLabel("certificateIssuerType", "ca_signed") + } + } +} + +func (my *SecurityCertificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { + var ( + expiryTimeMetric *matrix.Metric + ) + + instance.SetLabel("certificateExpiryStatus", "unknown") + + if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { + my.Logger.Error().Stack().Msg("missing expiry time metric") + return + } + + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { + // convert expiryTime from float64 to int64 and find difference + + timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() + + if timestampDiff <= 0 { + instance.SetLabel("certificateExpiryStatus", "expired") + } else { + // daysRemaining will be more than 0 if it has reached this point, convert to days + daysRemaining := timestampDiff / 24 + if daysRemaining < 60 { + instance.SetLabel("certificateExpiryStatus", "expiring") + } else { + instance.SetLabel("certificateExpiryStatus", "active") + } + } + } + +} + +func (my *SecurityCertificate) GetAdminVserver() (string, error) { + + var ( + result []gjson.Result + err error + adminVserver string + ) + + query := "api/private/cli/vserver" + href := rest.NewHrefBuilder(). + APIPath(query). + Fields([]string{"type"}). + Filter([]string{"type=admin"}). + Build() + + if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { + return "", err + } + + // This should be one iteration only as cluster can have one admin vserver + for _, svm := range result { + adminVserver = svm.Get("vserver").String() + } + return adminVserver, nil +} + +func (my *SecurityCertificate) GetSecuritySsl(adminSvm string) (string, error) { + + var ( + result []gjson.Result + err error + adminSerial string + ) + + query := "api/private/cli/security/ssl" + href := rest.NewHrefBuilder(). + APIPath(query). + Fields([]string{"serial"}). + Filter([]string{"vserver=" + adminSvm}). + Build() + + if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { + return "", err + } + + // This should be one iteration only as cluster can have one admin vserver + for _, ssl := range result { + adminSerial = ssl.Get("serial").String() + } + + return adminSerial, nil +} diff --git a/cmd/collectors/rest/rest.go b/cmd/collectors/rest/rest.go index 460cbbe1a..05ccd182b 100644 --- a/cmd/collectors/rest/rest.go +++ b/cmd/collectors/rest/rest.go @@ -14,6 +14,7 @@ import ( "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/qospolicyfixed" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/qtree" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securityaccount" + "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securitycertificate" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/shelf" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/snapmirror" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/svm" @@ -498,8 +499,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return volume.New(abc) case "VolumeAnalytics": return volumeanalytics.New(abc) - case "Certificate": - return certificate.New(abc) + case "SecurityCertificate": + return securitycertificate.New(abc) case "SVM": return svm.New(abc) case "Sensor": @@ -520,6 +521,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return systemnode.New(abc) case "Workload": return workload.New(abc) + case "Certificate": + return certificate.New(abc) default: r.Logger.Warn().Str("kind", kind).Msg("no rest plugin found ") } diff --git a/cmd/collectors/zapi/collector/zapi.go b/cmd/collectors/zapi/collector/zapi.go index 8da576e48..7ac340a3d 100644 --- a/cmd/collectors/zapi/collector/zapi.go +++ b/cmd/collectors/zapi/collector/zapi.go @@ -7,8 +7,8 @@ package zapi import ( "fmt" "github.com/netapp/harvest/v2/cmd/collectors" + "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securitycertificate" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/aggregate" - "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/certificate" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qospolicyadaptive" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qospolicyfixed" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qtree" @@ -162,8 +162,8 @@ func (z *Zapi) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return volume.New(abc) case "Sensor": return collectors.NewSensor(abc) - case "Certificate": - return certificate.New(abc) + case "SecurityCertificate": + return securitycertificate.New(abc) case "SVM": return svm.New(abc) case "Security": diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go similarity index 91% rename from cmd/collectors/zapi/plugins/certificate/certificate.go rename to cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go index 6c68718e3..9751f0d8b 100644 --- a/cmd/collectors/zapi/plugins/certificate/certificate.go +++ b/cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go @@ -2,7 +2,7 @@ * Copyright NetApp Inc, 2022 All rights reserved */ -package certificate +package securitycertificate import ( "crypto/x509" @@ -21,7 +21,7 @@ import ( const BatchSize = "500" -type Certificate struct { +type SecurityCertificate struct { *plugin.AbstractPlugin currentVal int batchSize string @@ -29,10 +29,10 @@ type Certificate struct { } func New(p *plugin.AbstractPlugin) plugin.Plugin { - return &Certificate{AbstractPlugin: p} + return &SecurityCertificate{AbstractPlugin: p} } -func (my *Certificate) Init() error { +func (my *SecurityCertificate) Init() error { var err error @@ -62,7 +62,7 @@ func (my *Certificate) Init() error { return nil } -func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { +func (my *SecurityCertificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { var ( adminVserver string @@ -116,7 +116,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, return nil, my.client.Metadata, nil } -func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance, certificateInstanceKey string) { +func (my *SecurityCertificate) setCertificateIssuerType(instance *matrix.Instance, certificateInstanceKey string) { var ( cert *x509.Certificate err error @@ -155,7 +155,7 @@ func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance, certi } } -func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { +func (my *SecurityCertificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { var ( expiryTimeMetric *matrix.Metric ) @@ -186,7 +186,7 @@ func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *mat } -func (my *Certificate) GetAdminVserver() (string, error) { +func (my *SecurityCertificate) GetAdminVserver() (string, error) { var ( result []*node.Node request *node.Node @@ -217,7 +217,7 @@ func (my *Certificate) GetAdminVserver() (string, error) { return adminVserver, nil } -func (my *Certificate) GetSecuritySsl(adminSvm string) (string, error) { +func (my *SecurityCertificate) GetSecuritySsl(adminSvm string) (string, error) { var ( result []*node.Node request *node.Node diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml index a8bda16dc..fb5dd121b 100644 --- a/conf/rest/9.12.0/certificate.yaml +++ b/conf/rest/9.12.0/certificate.yaml @@ -5,13 +5,22 @@ object: certificate counters: - ^^uuid + - ^expiry_time => expiry_time - ^name - - ^public_certificate => certificatePEM - - ^scope => scope - ^serial_number => serial_number - ^svm.name => svm - - ^type => type - - expiry_time(timestamp) => expiry_time + + +#plugins: +# - LabelAgent: +# split: +# # 2027-08-10T23:33:22+12:00 then +# - expiry_time `T` date,time # date:2027-08-10, time:23:33:22+12:00 +# - time `+` newTime,gmtPlus # newTime:23:33:22, gmtPlus: 12:00 +# - time `-` new1Time,gmtMinus # time:23:33:22, gmtMinus: empty + +plugins: + - Certificate export_options: instance_keys: @@ -19,6 +28,7 @@ export_options: - svm - uuid instance_labels: - - scope + - dateTime + - expiry_time + - gmt - serial_number - - type diff --git a/conf/rest/9.12.0/security_certificate.yaml b/conf/rest/9.12.0/security_certificate.yaml index 0c7d3a79e..03f78c820 100644 --- a/conf/rest/9.12.0/security_certificate.yaml +++ b/conf/rest/9.12.0/security_certificate.yaml @@ -17,7 +17,7 @@ counters: - type="server" plugins: - - Certificate: + - SecurityCertificate: schedule: - data: 3m # should be multiple of data poll duration diff --git a/conf/zapi/cdot/9.8.0/certificate.yaml b/conf/zapi/cdot/9.8.0/certificate.yaml deleted file mode 100644 index 96524fc4e..000000000 --- a/conf/zapi/cdot/9.8.0/certificate.yaml +++ /dev/null @@ -1,27 +0,0 @@ - -name: Certificate -query: security-certificate-get-iter -object: certificate - -counters: - certificate-info: - - ^^cert-name => name - - ^^serial-number => serial_number - - ^^vserver => svm - - ^public-certificate => certificatePEM - - ^type => type - - expiration-date => expiry_time - -plugins: - - LabelAgent: - join: - - uuid `_` name,serial_number,svm - -export_options: - instance_keys: - - name - - svm - - uuid - instance_labels: - - serial_number - - type diff --git a/conf/zapi/cdot/9.8.0/security_certificate.yaml b/conf/zapi/cdot/9.8.0/security_certificate.yaml index ad22c3437..ceadf38c2 100644 --- a/conf/zapi/cdot/9.8.0/security_certificate.yaml +++ b/conf/zapi/cdot/9.8.0/security_certificate.yaml @@ -16,7 +16,7 @@ plugins: - LabelAgent: include_equals: - type `server` - - Certificate: + - SecurityCertificate: schedule: - data: 3m # should be multiple of data poll duration diff --git a/conf/zapi/default.yaml b/conf/zapi/default.yaml index 131900b1e..86e87dd7d 100644 --- a/conf/zapi/default.yaml +++ b/conf/zapi/default.yaml @@ -8,7 +8,6 @@ schedule: objects: Aggregate: aggr.yaml AggregateEfficiency: aggr_efficiency.yaml - Certificate: certificate.yaml # The CIFSSession template may slow down data collection due to a high number of metrics. # CIFSSession: cifs_session.yaml # CIFSShare: cifs_share.yaml diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 67e187719..6c0e954a9 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1902,7 +1902,7 @@ }, { "datasource": "${DS_PROMETHEUS}", - "description": "This panel displays Certificate expiration time.", + "description": "This panel requires Harvest REST collector.\n\nThis panel displays Certificate expiration time.", "fieldConfig": { "defaults": { "color": { @@ -1933,7 +1933,7 @@ { "matcher": { "id": "byName", - "options": "Expire" + "options": "Expired" }, "properties": [ { @@ -1982,40 +1982,21 @@ "interval": "", "legendFormat": "", "refId": "A" - }, - { - "datasource": "${DS_PROMETHEUS}", - "editorMode": "code", - "exemplar": false, - "expr": "certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", svm=~\"$SVM\"} * 1000", - "format": "table", - "hide": false, - "instant": true, - "interval": "", - "legendFormat": "", - "refId": "B" } ], "title": "Certificates Detail", "transformations": [ - { - "id": "joinByField", - "options": { - "byField": "uuid", - "mode": "outer" - } - }, { "id": "filterFieldsByName", "options": { "include": { "names": [ "cluster", + "expiry_time", "name", - "scope", "svm", - "type", - "Value #B" + "dateTime", + "gmt" ] } } @@ -2023,29 +2004,23 @@ { "id": "organize", "options": { - "excludeByName": { - "cluster 2": true, - "datacenter 2": true, - "isEncrypted": true, - "isHardwareEncrypted": true, - "name 2": true, - "svm 2": true - }, + "excludeByName": {}, "includeByName": {}, "indexByName": { "cluster": 0, + "dateTime": 3, + "expiry_time": 5, + "gmt": 4, "name": 2, - "scope": 3, - "svm": 1, - "type": 4 + "svm": 1 }, "renameByName": { - "Value #B": "Expire", "cluster": "Cluster", + "dateTime": "Expiry Time", + "expiry_time": "Expired", + "gmt": "GMT", "name": "Certificate Name", - "scope": "Scope", - "svm": "SVM", - "type": "Type" + "svm": "SVM" } } } From d3d3f3c8657e9df79c34da4fe8b6b7098dbaed00 Mon Sep 17 00:00:00 2001 From: hardikl Date: Thu, 20 Jun 2024 19:04:55 +0530 Subject: [PATCH 03/14] feat: handled review comments --- .../rest/plugins/certificate/certificate.go | 226 +++++++++++++++-- .../certificateexpiry/certificateexpiry.go | 45 ++++ .../securitycertificate.go | 233 ------------------ cmd/collectors/rest/rest.go | 10 +- cmd/collectors/zapi/collector/zapi.go | 6 +- .../certificate.go} | 18 +- conf/rest/9.12.0/certificate.yaml | 11 +- conf/rest/9.12.0/security_certificate.yaml | 2 +- .../zapi/cdot/9.8.0/security_certificate.yaml | 2 +- 9 files changed, 272 insertions(+), 281 deletions(-) create mode 100644 cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go delete mode 100644 cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go rename cmd/collectors/zapi/plugins/{securitycertificate/securitycertificate.go => certificate/certificate.go} (91%) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 9dbca8bda..3325b801c 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -1,45 +1,233 @@ +/* + * Copyright NetApp Inc, 2022 All rights reserved + */ + package certificate import ( + "crypto/x509" + "encoding/pem" + "github.com/netapp/harvest/v2/cmd/collectors" "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/cmd/tools/rest" + "github.com/netapp/harvest/v2/pkg/conf" + ontap "github.com/netapp/harvest/v2/pkg/errs" "github.com/netapp/harvest/v2/pkg/matrix" "github.com/netapp/harvest/v2/pkg/util" - "strings" + "github.com/tidwall/gjson" + "time" ) type Certificate struct { *plugin.AbstractPlugin + currentVal int + client *rest.Client } func New(p *plugin.AbstractPlugin) plugin.Plugin { return &Certificate{AbstractPlugin: p} } +func (my *Certificate) Init() error { + + var err error + + if err := my.InitAbc(); err != nil { + return err + } + + timeout, _ := time.ParseDuration(rest.DefaultTimeout) + if my.client, err = rest.New(conf.ZapiPoller(my.ParentParams), timeout, my.Auth); err != nil { + my.Logger.Error().Stack().Err(err).Msg("connecting") + return err + } + + if err := my.client.Init(5); err != nil { + return err + } + + // Assigned the value to currentVal so that plugin would be invoked first time to populate cache. + my.currentVal = my.SetPluginInterval() + + return nil +} + func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { var ( - dateTimeVal string - gmt string + adminVserver string + adminVserverSerial string + err error ) data := dataMap[my.Object] + my.client.Metadata.Reset() + + if my.currentVal >= my.PluginInvocationRate { + my.currentVal = 0 - // update certificate instance based on admin vaserver serial - for _, certificateInstance := range data.GetInstances() { - expiryTime := certificateInstance.GetLabel("expiry_time") - // parsing 2027-08-10T23:33:22+12:00 and converting to Date:2027-08-10 23:33:22 GMT:+12:00 - fields := strings.Split(expiryTime, "T") - if strings.Contains(fields[1], "+") { - f := strings.Split(fields[1], "+") - dateTimeVal = fields[0] + " " + f[0] - gmt = "+" + f[1] - } else if strings.Contains(fields[1], "-") { - f := strings.Split(fields[1], "-") - dateTimeVal = fields[0] + " " + f[0] - gmt = "-" + f[1] + // invoke private vserver cli rest and get admin vserver name + if adminVserver, err = my.GetAdminVserver(); err != nil { + if ontap.IsRestErr(err, ontap.APINotFound) { + my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM") + } else { + my.Logger.Error().Err(err).Msg("Failed to collect admin SVM") + } + return nil, nil, nil } - certificateInstance.SetLabel("dateTime", dateTimeVal) - certificateInstance.SetLabel("gmt", gmt) + + // invoke private ssl cli rest and get the admin SVM's serial number + if adminVserverSerial, err = my.GetSecuritySsl(adminVserver); err != nil { + if ontap.IsRestErr(err, ontap.APINotFound) { + my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM's serial number") + } else { + my.Logger.Error().Msg("Failed to collect admin SVM's serial number") + } + return nil, nil, nil + } + + // update certificate instance based on admin vaserver serial + for _, certificateInstance := range data.GetInstances() { + if certificateInstance.IsExportable() { + certificateInstance.SetExportable(false) + serialNumber := certificateInstance.GetLabel("serial_number") + + if serialNumber == adminVserverSerial { + certificateInstance.SetExportable(true) + // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity + certificateInstance.SetLabel("svm", adminVserver) + my.setCertificateIssuerType(certificateInstance) + my.setCertificateValidity(data, certificateInstance) + } + } + } + + } + + my.currentVal++ + return nil, my.client.Metadata, nil +} + +func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance) { + var ( + cert *x509.Certificate + err error + ) + + certificatePEM := instance.GetLabel("certificatePEM") + certUUID := instance.GetLabel("uuid") + + if certificatePEM == "" { + my.Logger.Debug().Str("uuid", certUUID).Msg("Certificate is not found") + instance.SetLabel("certificateIssuerType", "unknown") + } else { + instance.SetLabel("certificateIssuerType", "self_signed") + certDecoded, _ := pem.Decode([]byte(certificatePEM)) + if certDecoded == nil { + my.Logger.Warn().Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") + instance.SetLabel("certificateIssuerType", "unknown") + return + } + + if cert, err = x509.ParseCertificate(certDecoded.Bytes); err != nil { + my.Logger.Warn().Err(err).Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") + instance.SetLabel("certificateIssuerType", "unknown") + return + } + + // Verifies if certificate is self-issued. This is true if the subject and issuer are equal. + if cert.Subject.String() == cert.Issuer.String() { + // Verifies if certificate is self-signed. This is true if the certificate is signed using its own public key. + if err = cert.CheckSignature(x509.SHA256WithRSA, cert.RawTBSCertificate, cert.Signature); err != nil { + // Any verification exception means it is not signed with the give key. i.e. not self-signed + instance.SetLabel("certificateIssuerType", "ca_signed") + } + } else { + instance.SetLabel("certificateIssuerType", "ca_signed") + } + } +} + +func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { + var ( + expiryTimeMetric *matrix.Metric + ) + + instance.SetLabel("certificateExpiryStatus", "unknown") + + if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { + my.Logger.Error().Stack().Msg("missing expiry time metric") + return + } + + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { + // convert expiryTime from float64 to int64 and find difference + + timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() + + if timestampDiff <= 0 { + instance.SetLabel("certificateExpiryStatus", "expired") + } else { + // daysRemaining will be more than 0 if it has reached this point, convert to days + daysRemaining := timestampDiff / 24 + if daysRemaining < 60 { + instance.SetLabel("certificateExpiryStatus", "expiring") + } else { + instance.SetLabel("certificateExpiryStatus", "active") + } + } + } + +} + +func (my *Certificate) GetAdminVserver() (string, error) { + + var ( + result []gjson.Result + err error + adminVserver string + ) + + query := "api/private/cli/vserver" + href := rest.NewHrefBuilder(). + APIPath(query). + Fields([]string{"type"}). + Filter([]string{"type=admin"}). + Build() + + if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { + return "", err + } + + // This should be one iteration only as cluster can have one admin vserver + for _, svm := range result { + adminVserver = svm.Get("vserver").String() + } + return adminVserver, nil +} + +func (my *Certificate) GetSecuritySsl(adminSvm string) (string, error) { + + var ( + result []gjson.Result + err error + adminSerial string + ) + + query := "api/private/cli/security/ssl" + href := rest.NewHrefBuilder(). + APIPath(query). + Fields([]string{"serial"}). + Filter([]string{"vserver=" + adminSvm}). + Build() + + if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { + return "", err + } + + // This should be one iteration only as cluster can have one admin vserver + for _, ssl := range result { + adminSerial = ssl.Get("serial").String() } - return nil, nil, nil + return adminSerial, nil } diff --git a/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go b/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go new file mode 100644 index 000000000..65bc53f19 --- /dev/null +++ b/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go @@ -0,0 +1,45 @@ +package certificateexpiry + +import ( + "github.com/netapp/harvest/v2/cmd/poller/plugin" + "github.com/netapp/harvest/v2/pkg/matrix" + "github.com/netapp/harvest/v2/pkg/util" + "strings" +) + +type CertificateExpiry struct { + *plugin.AbstractPlugin +} + +func New(p *plugin.AbstractPlugin) plugin.Plugin { + return &CertificateExpiry{AbstractPlugin: p} +} + +func (my *CertificateExpiry) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { + + var ( + dateTimeVal string + gmt string + ) + data := dataMap[my.Object] + + // update certificate instance based on admin vaserver serial + for _, certificateInstance := range data.GetInstances() { + expiryTime := certificateInstance.GetLabel("expiry_time") + // parsing 2027-08-10T23:33:22+12:00 and converting to Date:2027-08-10 23:33:22 GMT:+12:00 + fields := strings.Split(expiryTime, "T") + if strings.Contains(fields[1], "+") { + f := strings.Split(fields[1], "+") + dateTimeVal = fields[0] + " " + f[0] + gmt = "+" + f[1] + } else if strings.Contains(fields[1], "-") { + f := strings.Split(fields[1], "-") + dateTimeVal = fields[0] + " " + f[0] + gmt = "-" + f[1] + } + certificateInstance.SetLabel("dateTime", dateTimeVal) + certificateInstance.SetLabel("gmt", gmt) + } + + return nil, nil, nil +} diff --git a/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go b/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go deleted file mode 100644 index d558c7786..000000000 --- a/cmd/collectors/rest/plugins/securitycertificate/securitycertificate.go +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright NetApp Inc, 2022 All rights reserved - */ - -package securitycertificate - -import ( - "crypto/x509" - "encoding/pem" - "github.com/netapp/harvest/v2/cmd/collectors" - "github.com/netapp/harvest/v2/cmd/poller/plugin" - "github.com/netapp/harvest/v2/cmd/tools/rest" - "github.com/netapp/harvest/v2/pkg/conf" - ontap "github.com/netapp/harvest/v2/pkg/errs" - "github.com/netapp/harvest/v2/pkg/matrix" - "github.com/netapp/harvest/v2/pkg/util" - "github.com/tidwall/gjson" - "time" -) - -type SecurityCertificate struct { - *plugin.AbstractPlugin - currentVal int - client *rest.Client -} - -func New(p *plugin.AbstractPlugin) plugin.Plugin { - return &SecurityCertificate{AbstractPlugin: p} -} - -func (my *SecurityCertificate) Init() error { - - var err error - - if err := my.InitAbc(); err != nil { - return err - } - - timeout, _ := time.ParseDuration(rest.DefaultTimeout) - if my.client, err = rest.New(conf.ZapiPoller(my.ParentParams), timeout, my.Auth); err != nil { - my.Logger.Error().Stack().Err(err).Msg("connecting") - return err - } - - if err := my.client.Init(5); err != nil { - return err - } - - // Assigned the value to currentVal so that plugin would be invoked first time to populate cache. - my.currentVal = my.SetPluginInterval() - - return nil -} - -func (my *SecurityCertificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { - - var ( - adminVserver string - adminVserverSerial string - err error - ) - data := dataMap[my.Object] - my.client.Metadata.Reset() - - if my.currentVal >= my.PluginInvocationRate { - my.currentVal = 0 - - // invoke private vserver cli rest and get admin vserver name - if adminVserver, err = my.GetAdminVserver(); err != nil { - if ontap.IsRestErr(err, ontap.APINotFound) { - my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM") - } else { - my.Logger.Error().Err(err).Msg("Failed to collect admin SVM") - } - return nil, nil, nil - } - - // invoke private ssl cli rest and get the admin SVM's serial number - if adminVserverSerial, err = my.GetSecuritySsl(adminVserver); err != nil { - if ontap.IsRestErr(err, ontap.APINotFound) { - my.Logger.Debug().Err(err).Msg("Failed to collect admin SVM's serial number") - } else { - my.Logger.Error().Msg("Failed to collect admin SVM's serial number") - } - return nil, nil, nil - } - - // update certificate instance based on admin vaserver serial - for _, certificateInstance := range data.GetInstances() { - if certificateInstance.IsExportable() { - certificateInstance.SetExportable(false) - serialNumber := certificateInstance.GetLabel("serial_number") - - if serialNumber == adminVserverSerial { - certificateInstance.SetExportable(true) - // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity - certificateInstance.SetLabel("svm", adminVserver) - my.setCertificateIssuerType(certificateInstance) - my.setCertificateValidity(data, certificateInstance) - } - } - } - - } - - my.currentVal++ - return nil, my.client.Metadata, nil -} - -func (my *SecurityCertificate) setCertificateIssuerType(instance *matrix.Instance) { - var ( - cert *x509.Certificate - err error - ) - - certificatePEM := instance.GetLabel("certificatePEM") - certUUID := instance.GetLabel("uuid") - - if certificatePEM == "" { - my.Logger.Debug().Str("uuid", certUUID).Msg("Certificate is not found") - instance.SetLabel("certificateIssuerType", "unknown") - } else { - instance.SetLabel("certificateIssuerType", "self_signed") - certDecoded, _ := pem.Decode([]byte(certificatePEM)) - if certDecoded == nil { - my.Logger.Warn().Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") - instance.SetLabel("certificateIssuerType", "unknown") - return - } - - if cert, err = x509.ParseCertificate(certDecoded.Bytes); err != nil { - my.Logger.Warn().Err(err).Msg("PEM formatted object is not an X.509 certificate. Only PEM formatted X.509 certificate input is allowed") - instance.SetLabel("certificateIssuerType", "unknown") - return - } - - // Verifies if certificate is self-issued. This is true if the subject and issuer are equal. - if cert.Subject.String() == cert.Issuer.String() { - // Verifies if certificate is self-signed. This is true if the certificate is signed using its own public key. - if err = cert.CheckSignature(x509.SHA256WithRSA, cert.RawTBSCertificate, cert.Signature); err != nil { - // Any verification exception means it is not signed with the give key. i.e. not self-signed - instance.SetLabel("certificateIssuerType", "ca_signed") - } - } else { - instance.SetLabel("certificateIssuerType", "ca_signed") - } - } -} - -func (my *SecurityCertificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { - var ( - expiryTimeMetric *matrix.Metric - ) - - instance.SetLabel("certificateExpiryStatus", "unknown") - - if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { - my.Logger.Error().Stack().Msg("missing expiry time metric") - return - } - - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { - // convert expiryTime from float64 to int64 and find difference - - timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() - - if timestampDiff <= 0 { - instance.SetLabel("certificateExpiryStatus", "expired") - } else { - // daysRemaining will be more than 0 if it has reached this point, convert to days - daysRemaining := timestampDiff / 24 - if daysRemaining < 60 { - instance.SetLabel("certificateExpiryStatus", "expiring") - } else { - instance.SetLabel("certificateExpiryStatus", "active") - } - } - } - -} - -func (my *SecurityCertificate) GetAdminVserver() (string, error) { - - var ( - result []gjson.Result - err error - adminVserver string - ) - - query := "api/private/cli/vserver" - href := rest.NewHrefBuilder(). - APIPath(query). - Fields([]string{"type"}). - Filter([]string{"type=admin"}). - Build() - - if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { - return "", err - } - - // This should be one iteration only as cluster can have one admin vserver - for _, svm := range result { - adminVserver = svm.Get("vserver").String() - } - return adminVserver, nil -} - -func (my *SecurityCertificate) GetSecuritySsl(adminSvm string) (string, error) { - - var ( - result []gjson.Result - err error - adminSerial string - ) - - query := "api/private/cli/security/ssl" - href := rest.NewHrefBuilder(). - APIPath(query). - Fields([]string{"serial"}). - Filter([]string{"vserver=" + adminSvm}). - Build() - - if result, err = collectors.InvokeRestCall(my.client, href, my.Logger); err != nil { - return "", err - } - - // This should be one iteration only as cluster can have one admin vserver - for _, ssl := range result { - adminSerial = ssl.Get("serial").String() - } - - return adminSerial, nil -} diff --git a/cmd/collectors/rest/rest.go b/cmd/collectors/rest/rest.go index 05ccd182b..e6d16f8ef 100644 --- a/cmd/collectors/rest/rest.go +++ b/cmd/collectors/rest/rest.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/netapp/harvest/v2/cmd/collectors" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/certificate" + "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/certificateexpiry" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/disk" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/health" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/metroclustercheck" @@ -14,7 +15,6 @@ import ( "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/qospolicyfixed" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/qtree" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securityaccount" - "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securitycertificate" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/shelf" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/snapmirror" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/svm" @@ -499,8 +499,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return volume.New(abc) case "VolumeAnalytics": return volumeanalytics.New(abc) - case "SecurityCertificate": - return securitycertificate.New(abc) + case "Certificate": + return certificate.New(abc) case "SVM": return svm.New(abc) case "Sensor": @@ -521,8 +521,8 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return systemnode.New(abc) case "Workload": return workload.New(abc) - case "Certificate": - return certificate.New(abc) + case "CertificateExpiry": + return certificateexpiry.New(abc) default: r.Logger.Warn().Str("kind", kind).Msg("no rest plugin found ") } diff --git a/cmd/collectors/zapi/collector/zapi.go b/cmd/collectors/zapi/collector/zapi.go index 7ac340a3d..8da576e48 100644 --- a/cmd/collectors/zapi/collector/zapi.go +++ b/cmd/collectors/zapi/collector/zapi.go @@ -7,8 +7,8 @@ package zapi import ( "fmt" "github.com/netapp/harvest/v2/cmd/collectors" - "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/securitycertificate" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/aggregate" + "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/certificate" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qospolicyadaptive" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qospolicyfixed" "github.com/netapp/harvest/v2/cmd/collectors/zapi/plugins/qtree" @@ -162,8 +162,8 @@ func (z *Zapi) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return volume.New(abc) case "Sensor": return collectors.NewSensor(abc) - case "SecurityCertificate": - return securitycertificate.New(abc) + case "Certificate": + return certificate.New(abc) case "SVM": return svm.New(abc) case "Security": diff --git a/cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go similarity index 91% rename from cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go rename to cmd/collectors/zapi/plugins/certificate/certificate.go index 9751f0d8b..6c68718e3 100644 --- a/cmd/collectors/zapi/plugins/securitycertificate/securitycertificate.go +++ b/cmd/collectors/zapi/plugins/certificate/certificate.go @@ -2,7 +2,7 @@ * Copyright NetApp Inc, 2022 All rights reserved */ -package securitycertificate +package certificate import ( "crypto/x509" @@ -21,7 +21,7 @@ import ( const BatchSize = "500" -type SecurityCertificate struct { +type Certificate struct { *plugin.AbstractPlugin currentVal int batchSize string @@ -29,10 +29,10 @@ type SecurityCertificate struct { } func New(p *plugin.AbstractPlugin) plugin.Plugin { - return &SecurityCertificate{AbstractPlugin: p} + return &Certificate{AbstractPlugin: p} } -func (my *SecurityCertificate) Init() error { +func (my *Certificate) Init() error { var err error @@ -62,7 +62,7 @@ func (my *SecurityCertificate) Init() error { return nil } -func (my *SecurityCertificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { +func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { var ( adminVserver string @@ -116,7 +116,7 @@ func (my *SecurityCertificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix return nil, my.client.Metadata, nil } -func (my *SecurityCertificate) setCertificateIssuerType(instance *matrix.Instance, certificateInstanceKey string) { +func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance, certificateInstanceKey string) { var ( cert *x509.Certificate err error @@ -155,7 +155,7 @@ func (my *SecurityCertificate) setCertificateIssuerType(instance *matrix.Instanc } } -func (my *SecurityCertificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { +func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { var ( expiryTimeMetric *matrix.Metric ) @@ -186,7 +186,7 @@ func (my *SecurityCertificate) setCertificateValidity(data *matrix.Matrix, insta } -func (my *SecurityCertificate) GetAdminVserver() (string, error) { +func (my *Certificate) GetAdminVserver() (string, error) { var ( result []*node.Node request *node.Node @@ -217,7 +217,7 @@ func (my *SecurityCertificate) GetAdminVserver() (string, error) { return adminVserver, nil } -func (my *SecurityCertificate) GetSecuritySsl(adminSvm string) (string, error) { +func (my *Certificate) GetSecuritySsl(adminSvm string) (string, error) { var ( result []*node.Node request *node.Node diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml index fb5dd121b..4c323772c 100644 --- a/conf/rest/9.12.0/certificate.yaml +++ b/conf/rest/9.12.0/certificate.yaml @@ -10,17 +10,8 @@ counters: - ^serial_number => serial_number - ^svm.name => svm - -#plugins: -# - LabelAgent: -# split: -# # 2027-08-10T23:33:22+12:00 then -# - expiry_time `T` date,time # date:2027-08-10, time:23:33:22+12:00 -# - time `+` newTime,gmtPlus # newTime:23:33:22, gmtPlus: 12:00 -# - time `-` new1Time,gmtMinus # time:23:33:22, gmtMinus: empty - plugins: - - Certificate + - CertificateExpiry export_options: instance_keys: diff --git a/conf/rest/9.12.0/security_certificate.yaml b/conf/rest/9.12.0/security_certificate.yaml index 03f78c820..0c7d3a79e 100644 --- a/conf/rest/9.12.0/security_certificate.yaml +++ b/conf/rest/9.12.0/security_certificate.yaml @@ -17,7 +17,7 @@ counters: - type="server" plugins: - - SecurityCertificate: + - Certificate: schedule: - data: 3m # should be multiple of data poll duration diff --git a/conf/zapi/cdot/9.8.0/security_certificate.yaml b/conf/zapi/cdot/9.8.0/security_certificate.yaml index ceadf38c2..ad22c3437 100644 --- a/conf/zapi/cdot/9.8.0/security_certificate.yaml +++ b/conf/zapi/cdot/9.8.0/security_certificate.yaml @@ -16,7 +16,7 @@ plugins: - LabelAgent: include_equals: - type `server` - - SecurityCertificate: + - Certificate: schedule: - data: 3m # should be multiple of data poll duration From 46360f635dc3fcb9104ec4bb802b8c7d18f54b2c Mon Sep 17 00:00:00 2001 From: hardikl Date: Thu, 20 Jun 2024 20:29:19 +0530 Subject: [PATCH 04/14] feat: renamed the plugin name --- .../certificateexpiry/certificateexpiry.go | 45 ---------- cmd/collectors/rest/rest.go | 3 - conf/rest/9.12.0/certificate.yaml | 7 -- grafana/dashboards/cmode/security.json | 84 ++++++++++++++----- 4 files changed, 64 insertions(+), 75 deletions(-) delete mode 100644 cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go diff --git a/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go b/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go deleted file mode 100644 index 65bc53f19..000000000 --- a/cmd/collectors/rest/plugins/certificateexpiry/certificateexpiry.go +++ /dev/null @@ -1,45 +0,0 @@ -package certificateexpiry - -import ( - "github.com/netapp/harvest/v2/cmd/poller/plugin" - "github.com/netapp/harvest/v2/pkg/matrix" - "github.com/netapp/harvest/v2/pkg/util" - "strings" -) - -type CertificateExpiry struct { - *plugin.AbstractPlugin -} - -func New(p *plugin.AbstractPlugin) plugin.Plugin { - return &CertificateExpiry{AbstractPlugin: p} -} - -func (my *CertificateExpiry) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, *util.Metadata, error) { - - var ( - dateTimeVal string - gmt string - ) - data := dataMap[my.Object] - - // update certificate instance based on admin vaserver serial - for _, certificateInstance := range data.GetInstances() { - expiryTime := certificateInstance.GetLabel("expiry_time") - // parsing 2027-08-10T23:33:22+12:00 and converting to Date:2027-08-10 23:33:22 GMT:+12:00 - fields := strings.Split(expiryTime, "T") - if strings.Contains(fields[1], "+") { - f := strings.Split(fields[1], "+") - dateTimeVal = fields[0] + " " + f[0] - gmt = "+" + f[1] - } else if strings.Contains(fields[1], "-") { - f := strings.Split(fields[1], "-") - dateTimeVal = fields[0] + " " + f[0] - gmt = "-" + f[1] - } - certificateInstance.SetLabel("dateTime", dateTimeVal) - certificateInstance.SetLabel("gmt", gmt) - } - - return nil, nil, nil -} diff --git a/cmd/collectors/rest/rest.go b/cmd/collectors/rest/rest.go index e6d16f8ef..460cbbe1a 100644 --- a/cmd/collectors/rest/rest.go +++ b/cmd/collectors/rest/rest.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/netapp/harvest/v2/cmd/collectors" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/certificate" - "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/certificateexpiry" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/disk" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/health" "github.com/netapp/harvest/v2/cmd/collectors/rest/plugins/metroclustercheck" @@ -521,8 +520,6 @@ func (r *Rest) LoadPlugin(kind string, abc *plugin.AbstractPlugin) plugin.Plugin return systemnode.New(abc) case "Workload": return workload.New(abc) - case "CertificateExpiry": - return certificateexpiry.New(abc) default: r.Logger.Warn().Str("kind", kind).Msg("no rest plugin found ") } diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml index 4c323772c..35c98266e 100644 --- a/conf/rest/9.12.0/certificate.yaml +++ b/conf/rest/9.12.0/certificate.yaml @@ -7,19 +7,12 @@ counters: - ^^uuid - ^expiry_time => expiry_time - ^name - - ^serial_number => serial_number - ^svm.name => svm -plugins: - - CertificateExpiry - export_options: instance_keys: - name - svm - uuid instance_labels: - - dateTime - expiry_time - - gmt - - serial_number diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 6c0e954a9..b2c31eac3 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1933,7 +1933,7 @@ { "matcher": { "id": "byName", - "options": "Expired" + "options": "Expires In" }, "properties": [ { @@ -1941,6 +1941,50 @@ "value": "dateTimeFromNow" } ] + }, + { + "matcher": { + "id": "byName", + "options": "cluster" + }, + "properties": [ + { + "id": "displayName", + "value": "Cluster" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "/d/cdot-cluster/ontap-cluster?orgId=1&${Datacenter:queryparam}&${__url_time_range}&var-Cluster=${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "svm" + }, + "properties": [ + { + "id": "displayName", + "value": "SVM" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "/d/cdot-svm/ontap-svm?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-SVM=${__value.raw}" + } + ] + } + ] } ] }, @@ -1961,13 +2005,7 @@ ], "show": false }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "Value #B" - } - ] + "showHeader": true }, "pluginVersion": "10.3.1", "targets": [ @@ -1994,33 +2032,39 @@ "cluster", "expiry_time", "name", - "svm", - "dateTime", - "gmt" + "svm" ] } } }, + { + "id": "calculateField", + "options": { + "alias": "Expiry Date", + "mode": "reduceRow", + "reduce": { + "include": [ + "expiry_time" + ], + "reducer": "last" + } + } + }, { "id": "organize", "options": { "excludeByName": {}, "includeByName": {}, "indexByName": { + "Expiry Date": 3, "cluster": 0, - "dateTime": 3, - "expiry_time": 5, - "gmt": 4, + "expiry_time": 4, "name": 2, "svm": 1 }, "renameByName": { - "cluster": "Cluster", - "dateTime": "Expiry Time", - "expiry_time": "Expired", - "gmt": "GMT", - "name": "Certificate Name", - "svm": "SVM" + "expiry_time": "Expires In", + "name": "Certificate Name" } } } From 20f943b659e484035546ed9f31fcc7529f57672e Mon Sep 17 00:00:00 2001 From: hardikl Date: Fri, 21 Jun 2024 14:56:06 +0530 Subject: [PATCH 05/14] feat: handling review comments --- conf/rest/9.12.0/certificate.yaml | 9 +++++++-- container/prometheus/alert_rules.yml | 12 +++++++++++- grafana/dashboards/cmode/security.json | 20 +++++++++++++------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml index 35c98266e..7da8a7be3 100644 --- a/conf/rest/9.12.0/certificate.yaml +++ b/conf/rest/9.12.0/certificate.yaml @@ -7,12 +7,17 @@ counters: - ^^uuid - ^expiry_time => expiry_time - ^name + - ^scope => scope - ^svm.name => svm + - ^type => type + - expiry_time(timestamp) => expiry_time export_options: instance_keys: + - expiry_time - name - - svm - uuid instance_labels: - - expiry_time + - scope + - svm + - type diff --git a/container/prometheus/alert_rules.yml b/container/prometheus/alert_rules.yml index 5b1089b3d..a002bc41f 100644 --- a/container/prometheus/alert_rules.yml +++ b/container/prometheus/alert_rules.yml @@ -101,4 +101,14 @@ groups: severity: "warning" annotations: summary: "{{ $labels.object }} [{{ $labels.volume }}] deleted" - description: "{{ $labels.object }} [{{ $labels.volume }}] deleted" \ No newline at end of file + description: "{{ $labels.object }} [{{ $labels.volume }}] deleted" + + # Certificates expiring within 1 month + - alert: Certificates expiring within 1 month + expr: 0 < (certificate_expiry_time{} - time()) < (30*24*3600) + for: 1m + labels: + severity: "warning" + annotations: + summary: "Certificate [{{ $labels.name }}] will be expiring on [{{ $labels.expiry_time }}]" + description: "Certificate [{{ $labels.name }}] will be expiring on [{{ $labels.expiry_time }}]" \ No newline at end of file diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index b2c31eac3..aa9c81011 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -2013,7 +2013,7 @@ "datasource": "${DS_PROMETHEUS}", "editorMode": "code", "exemplar": false, - "expr": "certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", svm=~\"$SVM\"}", + "expr": "certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}", "format": "table", "hide": false, "instant": true, @@ -2022,7 +2022,7 @@ "refId": "A" } ], - "title": "Certificates Detail", + "title": "SSL Certificates Expiration", "transformations": [ { "id": "filterFieldsByName", @@ -2032,7 +2032,9 @@ "cluster", "expiry_time", "name", - "svm" + "scope", + "svm", + "type" ] } } @@ -2056,15 +2058,19 @@ "excludeByName": {}, "includeByName": {}, "indexByName": { - "Expiry Date": 3, + "Expiry Date": 5, "cluster": 0, - "expiry_time": 4, + "expiry_time": 6, "name": 2, - "svm": 1 + "scope": 4, + "svm": 1, + "type": 3 }, "renameByName": { "expiry_time": "Expires In", - "name": "Certificate Name" + "name": "Certificate Name", + "type": "Type", + "scope": "Scope" } } } From ef462ae47cec34acd5aff8da35ba0f66f8ef268c Mon Sep 17 00:00:00 2001 From: hardikl Date: Fri, 21 Jun 2024 15:00:13 +0530 Subject: [PATCH 06/14] feat: minor change --- grafana/dashboards/cmode/security.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index aa9c81011..d001a502b 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -2069,8 +2069,8 @@ "renameByName": { "expiry_time": "Expires In", "name": "Certificate Name", - "type": "Type", - "scope": "Scope" + "scope": "Scope", + "type": "Type" } } } From 618b64969ad523c9b76736b9a4a23e94ab88b5e4 Mon Sep 17 00:00:00 2001 From: hardikl Date: Fri, 21 Jun 2024 16:38:18 +0530 Subject: [PATCH 07/14] feat: added alert for expired certificates --- container/prometheus/alert_rules.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/container/prometheus/alert_rules.yml b/container/prometheus/alert_rules.yml index a002bc41f..cfbc26396 100644 --- a/container/prometheus/alert_rules.yml +++ b/container/prometheus/alert_rules.yml @@ -111,4 +111,13 @@ groups: severity: "warning" annotations: summary: "Certificate [{{ $labels.name }}] will be expiring on [{{ $labels.expiry_time }}]" - description: "Certificate [{{ $labels.name }}] will be expiring on [{{ $labels.expiry_time }}]" \ No newline at end of file + description: "Certificate [{{ $labels.name }}] will be expiring on [{{ $labels.expiry_time }}]" + + # Certificates expired + - alert: Certificates expired + expr: (certificate_expiry_time{} - time()) < 0 + labels: + severity: "critical" + annotations: + summary: "Certificate [{{ $labels.name }}] has been expired on [{{ $labels.expiry_time }}]" + description: "Certificate [{{ $labels.name }}] has been expired on [{{ $labels.expiry_time }}]" \ No newline at end of file From 75352c9b580df3dce3e4f69ae196c17e6c8065f6 Mon Sep 17 00:00:00 2001 From: hardikl Date: Mon, 24 Jun 2024 16:56:24 +0530 Subject: [PATCH 08/14] feat: reuse template and add color in dashboard table --- .../rest/plugins/certificate/certificate.go | 2 - .../zapi/plugins/certificate/certificate.go | 2 - conf/rest/9.12.0/certificate.yaml | 23 --- conf/rest/9.12.0/security_certificate.yaml | 5 +- conf/rest/default.yaml | 1 - .../zapi/cdot/9.8.0/security_certificate.yaml | 4 +- grafana/dashboards/cmode/security.json | 131 +++++++++++++----- 7 files changed, 101 insertions(+), 67 deletions(-) delete mode 100644 conf/rest/9.12.0/certificate.yaml diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 3325b801c..b774d75b0 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -88,11 +88,9 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, // update certificate instance based on admin vaserver serial for _, certificateInstance := range data.GetInstances() { if certificateInstance.IsExportable() { - certificateInstance.SetExportable(false) serialNumber := certificateInstance.GetLabel("serial_number") if serialNumber == adminVserverSerial { - certificateInstance.SetExportable(true) // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity certificateInstance.SetLabel("svm", adminVserver) my.setCertificateIssuerType(certificateInstance) diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go index 6c68718e3..5984b9286 100644 --- a/cmd/collectors/zapi/plugins/certificate/certificate.go +++ b/cmd/collectors/zapi/plugins/certificate/certificate.go @@ -99,11 +99,9 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, // update certificate instance based on admin vaserver serial for certificateInstanceKey, certificateInstance := range data.GetInstances() { if certificateInstance.IsExportable() { - certificateInstance.SetExportable(false) serialNumber := certificateInstance.GetLabel("serial_number") if serialNumber == adminVserverSerial { - certificateInstance.SetExportable(true) my.setCertificateIssuerType(certificateInstance, certificateInstanceKey) my.setCertificateValidity(data, certificateInstance) } diff --git a/conf/rest/9.12.0/certificate.yaml b/conf/rest/9.12.0/certificate.yaml deleted file mode 100644 index 7da8a7be3..000000000 --- a/conf/rest/9.12.0/certificate.yaml +++ /dev/null @@ -1,23 +0,0 @@ - -name: Certificate -query: api/security/certificates -object: certificate - -counters: - - ^^uuid - - ^expiry_time => expiry_time - - ^name - - ^scope => scope - - ^svm.name => svm - - ^type => type - - expiry_time(timestamp) => expiry_time - -export_options: - instance_keys: - - expiry_time - - name - - uuid - instance_labels: - - scope - - svm - - type diff --git a/conf/rest/9.12.0/security_certificate.yaml b/conf/rest/9.12.0/security_certificate.yaml index 0c7d3a79e..eb1e95c75 100644 --- a/conf/rest/9.12.0/security_certificate.yaml +++ b/conf/rest/9.12.0/security_certificate.yaml @@ -5,6 +5,7 @@ object: security_certificate counters: - ^^uuid + - ^expiry_time => expiry_time - ^name - ^public_certificate => certificatePEM - ^scope => scope @@ -12,9 +13,6 @@ counters: - ^svm.name => svm - ^type => type - expiry_time(timestamp) => expiry_time - - filter: - - scope=!"svm" - - type="server" plugins: - Certificate: @@ -23,6 +21,7 @@ plugins: export_options: instance_keys: + - expiry_time - uuid instance_labels: - certificateExpiryStatus diff --git a/conf/rest/default.yaml b/conf/rest/default.yaml index afdc7f53c..6676c43b8 100644 --- a/conf/rest/default.yaml +++ b/conf/rest/default.yaml @@ -10,7 +10,6 @@ schedule: objects: Aggregate: aggr.yaml - Certificate: certificate.yaml # The CIFSSession template may slow down data collection due to a high number of metrics. # CIFSSession: cifs_session.yaml # CIFSShare: cifs_share.yaml diff --git a/conf/zapi/cdot/9.8.0/security_certificate.yaml b/conf/zapi/cdot/9.8.0/security_certificate.yaml index ad22c3437..5b8b8b984 100644 --- a/conf/zapi/cdot/9.8.0/security_certificate.yaml +++ b/conf/zapi/cdot/9.8.0/security_certificate.yaml @@ -13,9 +13,6 @@ counters: - expiration-date => expiry_time plugins: - - LabelAgent: - include_equals: - - type `server` - Certificate: schedule: - data: 3m # should be multiple of data poll duration @@ -28,4 +25,5 @@ export_options: instance_labels: - certificateExpiryStatus - certificateIssuerType + - expiry_time - type diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index d001a502b..bc408bb71 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1911,11 +1911,8 @@ }, "custom": { "align": "left", - "cellOptions": { - "type": "auto" - }, - "filterable": true, - "inspect": false + "displayMode": "color-background-solid", + "filterable": true }, "mappings": [], "thresholds": { @@ -1924,10 +1921,14 @@ { "color": "green", "value": null + }, + { + "color": "red", + "value": "" } ] }, - "unitScale": true + "unit": "locale" }, "overrides": [ { @@ -1945,12 +1946,12 @@ { "matcher": { "id": "byName", - "options": "cluster" + "options": "svm" }, "properties": [ { "id": "displayName", - "value": "Cluster" + "value": "SVM" }, { "id": "links", @@ -1958,7 +1959,7 @@ { "targetBlank": true, "title": "", - "url": "/d/cdot-cluster/ontap-cluster?orgId=1&${Datacenter:queryparam}&${__url_time_range}&var-Cluster=${__value.raw}" + "url": "/d/cdot-svm/ontap-svm?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-SVM=${__value.raw}" } ] } @@ -1967,24 +1968,63 @@ { "matcher": { "id": "byName", - "options": "svm" + "options": "Expiry" }, "properties": [ { - "id": "displayName", - "value": "SVM" - }, - { - "id": "links", + "id": "mappings", "value": [ { - "targetBlank": true, - "title": "", - "url": "/d/cdot-svm/ontap-svm?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-SVM=${__value.raw}" + "options": { + "from": -100000000000000000000, + "result": { + "color": "red", + "index": 0, + "text": "Expired" + }, + "to": 0 + }, + "type": "range" + }, + { + "options": { + "from": 0, + "result": { + "color": "orange", + "index": 1, + "text": "This Month" + }, + "to": 2678400 + }, + "type": "range" + }, + { + "options": { + "from": 2678400, + "result": { + "color": "green", + "index": 2, + "text": "OK" + }, + "to": 100000000000000000000 + }, + "type": "range" } ] } ] + }, + { + "matcher": { + "id": "byName", + "options": "Expiry Date" + }, + "properties": [ + { + "id": "custom.width", + "value": 204 + } + ] } ] }, @@ -2005,36 +2045,58 @@ ], "show": false }, - "showHeader": true + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Expiry" + } + ] }, - "pluginVersion": "10.3.1", + "pluginVersion": "8.3.4", "targets": [ { "datasource": "${DS_PROMETHEUS}", "editorMode": "code", "exemplar": false, - "expr": "certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}", + "expr": "security_certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}", "format": "table", "hide": false, "instant": true, "interval": "", "legendFormat": "", "refId": "A" + }, + { + "datasource": "${DS_PROMETHEUS}", + "exemplar": false, + "expr": "security_certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}-time()", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" } ], "title": "SSL Certificates Expiration", "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "expiry_time" + } + }, { "id": "filterFieldsByName", "options": { "include": { "names": [ - "cluster", "expiry_time", - "name", "scope", "svm", - "type" + "type", + "Value #B" ] } } @@ -2055,18 +2117,21 @@ { "id": "organize", "options": { - "excludeByName": {}, + "excludeByName": { + "Value #B": false + }, "includeByName": {}, "indexByName": { - "Expiry Date": 5, - "cluster": 0, - "expiry_time": 6, - "name": 2, - "scope": 4, - "svm": 1, - "type": 3 + "Expiry Date": 3, + "Value #B": 4, + "expiry_time": 5, + "scope": 2, + "svm": 0, + "type": 1 }, "renameByName": { + "Expiry Date": "", + "Value #B": "Expiry", "expiry_time": "Expires In", "name": "Certificate Name", "scope": "Scope", @@ -3764,7 +3829,7 @@ }, { "exemplar": false, - "expr": "security_certificate_labels{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\"}", + "expr": "security_certificate_labels{datacenter=~\"$Datacenter\",cluster=~\"$Cluster\",certificateExpiryStatus!=\"\",certificateIssuerType!=\"\"}", "format": "table", "hide": false, "instant": true, From 3bd1170b98df4657351e13caa0e47526507bcebb Mon Sep 17 00:00:00 2001 From: hardikl Date: Mon, 24 Jun 2024 20:08:14 +0530 Subject: [PATCH 09/14] feat: handling review comments --- grafana/dashboards/cmode/security.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index bc408bb71..615218fb8 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -2059,7 +2059,7 @@ "datasource": "${DS_PROMETHEUS}", "editorMode": "code", "exemplar": false, - "expr": "security_certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}", + "expr": "security_certificate_labels{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", expiry_time!=\"\"}", "format": "table", "hide": false, "instant": true, @@ -2070,7 +2070,7 @@ { "datasource": "${DS_PROMETHEUS}", "exemplar": false, - "expr": "security_certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}-time()", + "expr": "security_certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", expiry_time!=\"\"}-time()", "format": "table", "hide": false, "instant": true, From adfa58f4fafd6be895a61bb05c283cd9cd04f66c Mon Sep 17 00:00:00 2001 From: hardikl Date: Tue, 25 Jun 2024 16:22:54 +0530 Subject: [PATCH 10/14] feat: couple of changes in expiry_time for supporting zapi --- .../rest/plugins/certificate/certificate.go | 56 ++++++------ .../zapi/plugins/certificate/certificate.go | 56 ++++++------ conf/rest/9.12.0/security_certificate.yaml | 3 +- .../zapi/cdot/9.8.0/security_certificate.yaml | 7 +- container/prometheus/alert_rules.yml | 4 +- grafana/dashboards/cmode/security.json | 87 ++++++++++++++++--- 6 files changed, 140 insertions(+), 73 deletions(-) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index b774d75b0..8ea59d960 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -57,6 +57,8 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, var ( adminVserver string adminVserverSerial string + expiryTimeMetric *matrix.Metric + unixTime time.Time err error ) data := dataMap[my.Object] @@ -87,18 +89,31 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, // update certificate instance based on admin vaserver serial for _, certificateInstance := range data.GetInstances() { + unixTime = time.Now() if certificateInstance.IsExportable() { serialNumber := certificateInstance.GetLabel("serial_number") + scope := certificateInstance.GetLabel("scope") + CertType := certificateInstance.GetLabel("type") - if serialNumber == adminVserverSerial { + if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { + my.Logger.Error().Stack().Msg("missing expiry time metric") + continue + } + + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { + // convert expiryTime from float64 to int64 and then to unix Time + unixTime = time.Unix(int64(expiryTime), 0) + certificateInstance.SetLabel("expiry_time", unixTime.UTC().String()) + } + + if serialNumber == adminVserverSerial && scope == "cluster" && CertType == "server" { // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity certificateInstance.SetLabel("svm", adminVserver) my.setCertificateIssuerType(certificateInstance) - my.setCertificateValidity(data, certificateInstance) + my.setCertificateValidity(unixTime, certificateInstance) } } } - } my.currentVal++ @@ -145,36 +160,23 @@ func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance) { } } -func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { - var ( - expiryTimeMetric *matrix.Metric - ) - +func (my *Certificate) setCertificateValidity(unixTime time.Time, instance *matrix.Instance) { instance.SetLabel("certificateExpiryStatus", "unknown") - if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { - my.Logger.Error().Stack().Msg("missing expiry time metric") - return - } - - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { - // convert expiryTime from float64 to int64 and find difference + // find difference from unix Time + timestampDiff := time.Until(unixTime).Hours() - timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() - - if timestampDiff <= 0 { - instance.SetLabel("certificateExpiryStatus", "expired") + if timestampDiff <= 0 { + instance.SetLabel("certificateExpiryStatus", "expired") + } else { + // daysRemaining will be more than 0 if it has reached this point, convert to days + daysRemaining := timestampDiff / 24 + if daysRemaining < 60 { + instance.SetLabel("certificateExpiryStatus", "expiring") } else { - // daysRemaining will be more than 0 if it has reached this point, convert to days - daysRemaining := timestampDiff / 24 - if daysRemaining < 60 { - instance.SetLabel("certificateExpiryStatus", "expiring") - } else { - instance.SetLabel("certificateExpiryStatus", "active") - } + instance.SetLabel("certificateExpiryStatus", "active") } } - } func (my *Certificate) GetAdminVserver() (string, error) { diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go index 5984b9286..87748c24d 100644 --- a/cmd/collectors/zapi/plugins/certificate/certificate.go +++ b/cmd/collectors/zapi/plugins/certificate/certificate.go @@ -67,6 +67,8 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, var ( adminVserver string adminVserverSerial string + expiryTimeMetric *matrix.Metric + unixTime time.Time err error ) @@ -98,16 +100,30 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, // update certificate instance based on admin vaserver serial for certificateInstanceKey, certificateInstance := range data.GetInstances() { + unixTime = time.Now() if certificateInstance.IsExportable() { + name := certificateInstance.GetLabel("name") serialNumber := certificateInstance.GetLabel("serial_number") + svm := certificateInstance.GetLabel("svm") + CertType := certificateInstance.GetLabel("type") + certificateInstance.SetLabel("uuid", name+serialNumber+svm) - if serialNumber == adminVserverSerial { + if expiryTimeMetric = data.GetMetric("certificate-info.expiration-date"); expiryTimeMetric == nil { + my.Logger.Error().Msg("missing expiry time metric") + continue + } + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { + // convert expiryTime from float64 to int64 and then to unix Time + unixTime = time.Unix(int64(expiryTime), 0) + certificateInstance.SetLabel("expiry_time", unixTime.UTC().String()) + } + + if serialNumber == adminVserverSerial && CertType == "server" { my.setCertificateIssuerType(certificateInstance, certificateInstanceKey) - my.setCertificateValidity(data, certificateInstance) + my.setCertificateValidity(unixTime, certificateInstance) } } } - } my.currentVal++ @@ -153,35 +169,23 @@ func (my *Certificate) setCertificateIssuerType(instance *matrix.Instance, certi } } -func (my *Certificate) setCertificateValidity(data *matrix.Matrix, instance *matrix.Instance) { - var ( - expiryTimeMetric *matrix.Metric - ) - +func (my *Certificate) setCertificateValidity(unixTime time.Time, instance *matrix.Instance) { instance.SetLabel("certificateExpiryStatus", "unknown") - if expiryTimeMetric = data.GetMetric("certificate-info.expiration-date"); expiryTimeMetric == nil { - my.Logger.Error().Msg("missing expiry time metric") - return - } - - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(instance); ok { - // convert expiryTime from float64 to int64 and find difference - timestampDiff := time.Until(time.Unix(int64(expiryTime), 0)).Hours() + // find difference from unix Time + timestampDiff := time.Until(unixTime).Hours() - if timestampDiff <= 0 { - instance.SetLabel("certificateExpiryStatus", "expired") + if timestampDiff <= 0 { + instance.SetLabel("certificateExpiryStatus", "expired") + } else { + // daysRemaining will be more than 0 if it has reached this point, convert to days + daysRemaining := timestampDiff / 24 + if daysRemaining < 60 { + instance.SetLabel("certificateExpiryStatus", "expiring") } else { - // daysRemaining will be more than 0 if it has reached this point, convert to days - daysRemaining := timestampDiff / 24 - if daysRemaining < 60 { - instance.SetLabel("certificateExpiryStatus", "expiring") - } else { - instance.SetLabel("certificateExpiryStatus", "active") - } + instance.SetLabel("certificateExpiryStatus", "active") } } - } func (my *Certificate) GetAdminVserver() (string, error) { diff --git a/conf/rest/9.12.0/security_certificate.yaml b/conf/rest/9.12.0/security_certificate.yaml index eb1e95c75..4491f153a 100644 --- a/conf/rest/9.12.0/security_certificate.yaml +++ b/conf/rest/9.12.0/security_certificate.yaml @@ -5,7 +5,6 @@ object: security_certificate counters: - ^^uuid - - ^expiry_time => expiry_time - ^name - ^public_certificate => certificatePEM - ^scope => scope @@ -21,11 +20,11 @@ plugins: export_options: instance_keys: - - expiry_time - uuid instance_labels: - certificateExpiryStatus - certificateIssuerType + - expiry_time - name - scope - serial_number diff --git a/conf/zapi/cdot/9.8.0/security_certificate.yaml b/conf/zapi/cdot/9.8.0/security_certificate.yaml index 5b8b8b984..765eb0e2f 100644 --- a/conf/zapi/cdot/9.8.0/security_certificate.yaml +++ b/conf/zapi/cdot/9.8.0/security_certificate.yaml @@ -19,11 +19,12 @@ plugins: export_options: instance_keys: - - name - - serial_number - - svm + - uuid instance_labels: - certificateExpiryStatus - certificateIssuerType - expiry_time + - name + - serial_number + - svm - type diff --git a/container/prometheus/alert_rules.yml b/container/prometheus/alert_rules.yml index cfbc26396..d4bba70f5 100644 --- a/container/prometheus/alert_rules.yml +++ b/container/prometheus/alert_rules.yml @@ -105,7 +105,7 @@ groups: # Certificates expiring within 1 month - alert: Certificates expiring within 1 month - expr: 0 < (certificate_expiry_time{} - time()) < (30*24*3600) + expr: 0 < (security_certificate_expiry_time{} - time()) < (30*24*3600) for: 1m labels: severity: "warning" @@ -115,7 +115,7 @@ groups: # Certificates expired - alert: Certificates expired - expr: (certificate_expiry_time{} - time()) < 0 + expr: (security_certificate_expiry_time{} - time()) < 0 labels: severity: "critical" annotations: diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 615218fb8..707d1cd27 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1965,6 +1965,50 @@ } ] }, + { + "matcher": { + "id": "byName", + "options": "cluster" + }, + "properties": [ + { + "id": "displayName", + "value": "Cluster" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "/d/cdot-cluster/ontap-cluster?orgId=1&${Datacenter:queryparam}&${__url_time_range}&var-Cluster=${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "datacenter" + }, + "properties": [ + { + "id": "displayName", + "value": "Datacenter" + }, + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "/d/cdot-datacenter/ontap-datacenter?orgId=1&${__url_time_range}&var-Datacenter=${__value.raw}" + } + ] + } + ] + }, { "matcher": { "id": "byName", @@ -1992,15 +2036,15 @@ "result": { "color": "orange", "index": 1, - "text": "This Month" + "text": "Within 2 Months" }, - "to": 2678400 + "to": 5184000 }, "type": "range" }, { "options": { - "from": 2678400, + "from": 5184000, "result": { "color": "green", "index": 2, @@ -2070,7 +2114,7 @@ { "datasource": "${DS_PROMETHEUS}", "exemplar": false, - "expr": "security_certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\", expiry_time!=\"\"}-time()", + "expr": "security_certificate_expiry_time{datacenter=~\"$Datacenter\", cluster=~\"$Cluster\"}-time()", "format": "table", "hide": false, "instant": true, @@ -2084,7 +2128,14 @@ { "id": "seriesToColumns", "options": { - "byField": "expiry_time" + "byField": "uuid" + } + }, + { + "id": "renameByRegex", + "options": { + "regex": "(.*) 1$", + "renamePattern": "$1" } }, { @@ -2092,11 +2143,14 @@ "options": { "include": { "names": [ + "cluster", + "datacenter", "expiry_time", "scope", "svm", "type", - "Value #B" + "Value #B", + "name" ] } } @@ -2118,16 +2172,23 @@ "id": "organize", "options": { "excludeByName": { - "Value #B": false + "Value #B": false, + "cluster 2": true, + "datacenter 2": true }, "includeByName": {}, "indexByName": { - "Expiry Date": 3, - "Value #B": 4, - "expiry_time": 5, - "scope": 2, - "svm": 0, - "type": 1 + "Expiry Date": 6, + "Value #B": 7, + "cluster": 1, + "cluster 2": 9, + "datacenter": 0, + "datacenter 2": 10, + "expiry_time": 8, + "name": 5, + "scope": 4, + "svm": 2, + "type": 3 }, "renameByName": { "Expiry Date": "", From b2f431202d719e9988b201d40b49afbaefb43624 Mon Sep 17 00:00:00 2001 From: hardikl Date: Tue, 25 Jun 2024 19:05:35 +0530 Subject: [PATCH 11/14] feat: handled review comments --- .../rest/plugins/certificate/certificate.go | 51 ++++++++++--------- .../zapi/plugins/certificate/certificate.go | 49 +++++++++--------- grafana/dashboards/cmode/security.json | 2 +- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 8ea59d960..e0db1fafb 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -87,31 +87,34 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, return nil, nil, nil } - // update certificate instance based on admin vaserver serial + // update certificate instance based on admin vserver serial for _, certificateInstance := range data.GetInstances() { - unixTime = time.Now() - if certificateInstance.IsExportable() { - serialNumber := certificateInstance.GetLabel("serial_number") - scope := certificateInstance.GetLabel("scope") - CertType := certificateInstance.GetLabel("type") - - if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { - my.Logger.Error().Stack().Msg("missing expiry time metric") - continue - } - - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { - // convert expiryTime from float64 to int64 and then to unix Time - unixTime = time.Unix(int64(expiryTime), 0) - certificateInstance.SetLabel("expiry_time", unixTime.UTC().String()) - } - - if serialNumber == adminVserverSerial && scope == "cluster" && CertType == "server" { - // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity - certificateInstance.SetLabel("svm", adminVserver) - my.setCertificateIssuerType(certificateInstance) - my.setCertificateValidity(unixTime, certificateInstance) - } + if !certificateInstance.IsExportable() { + continue + } + serialNumber := certificateInstance.GetLabel("serial_number") + scope := certificateInstance.GetLabel("scope") + CertType := certificateInstance.GetLabel("type") + + if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { + my.Logger.Error().Stack().Msg("missing expiry time metric") + continue + } + + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { + // convert expiryTime from float64 to int64 and then to unix Time + unixTime = time.Unix(int64(expiryTime), 0) + certificateInstance.SetLabel("expiry_time", unixTime.UTC().Format(time.RFC3339)) + } else { + // This is fail-safe case + unixTime = time.Now() + } + + if serialNumber == adminVserverSerial && scope == "cluster" && CertType == "server" { + // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity + certificateInstance.SetLabel("svm", adminVserver) + my.setCertificateIssuerType(certificateInstance) + my.setCertificateValidity(unixTime, certificateInstance) } } } diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go index 87748c24d..57c72096e 100644 --- a/cmd/collectors/zapi/plugins/certificate/certificate.go +++ b/cmd/collectors/zapi/plugins/certificate/certificate.go @@ -98,30 +98,33 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, return nil, nil, nil } - // update certificate instance based on admin vaserver serial + // update certificate instance based on admin vserver serial for certificateInstanceKey, certificateInstance := range data.GetInstances() { - unixTime = time.Now() - if certificateInstance.IsExportable() { - name := certificateInstance.GetLabel("name") - serialNumber := certificateInstance.GetLabel("serial_number") - svm := certificateInstance.GetLabel("svm") - CertType := certificateInstance.GetLabel("type") - certificateInstance.SetLabel("uuid", name+serialNumber+svm) - - if expiryTimeMetric = data.GetMetric("certificate-info.expiration-date"); expiryTimeMetric == nil { - my.Logger.Error().Msg("missing expiry time metric") - continue - } - if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { - // convert expiryTime from float64 to int64 and then to unix Time - unixTime = time.Unix(int64(expiryTime), 0) - certificateInstance.SetLabel("expiry_time", unixTime.UTC().String()) - } - - if serialNumber == adminVserverSerial && CertType == "server" { - my.setCertificateIssuerType(certificateInstance, certificateInstanceKey) - my.setCertificateValidity(unixTime, certificateInstance) - } + if !certificateInstance.IsExportable() { + continue + } + name := certificateInstance.GetLabel("name") + serialNumber := certificateInstance.GetLabel("serial_number") + svm := certificateInstance.GetLabel("svm") + CertType := certificateInstance.GetLabel("type") + certificateInstance.SetLabel("uuid", name+serialNumber+svm) + + if expiryTimeMetric = data.GetMetric("certificate-info.expiration-date"); expiryTimeMetric == nil { + my.Logger.Error().Msg("missing expiry time metric") + continue + } + if expiryTime, ok := expiryTimeMetric.GetValueFloat64(certificateInstance); ok { + // convert expiryTime from float64 to int64 and then to unix Time + unixTime = time.Unix(int64(expiryTime), 0) + certificateInstance.SetLabel("expiry_time", unixTime.UTC().Format(time.RFC3339)) + } else { + // This is fail-safe case + unixTime = time.Now() + } + + if serialNumber == adminVserverSerial && CertType == "server" { + my.setCertificateIssuerType(certificateInstance, certificateInstanceKey) + my.setCertificateValidity(unixTime, certificateInstance) } } } diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 707d1cd27..3f0619aa3 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1405,7 +1405,7 @@ "mode": "markdown" }, "pluginVersion": "8.1.8", - "title": "Certificates", + "title": "Admin Certificates", "type": "text" }, { From 457ee5162046587e3e42adb6faa2cb4b35f139b1 Mon Sep 17 00:00:00 2001 From: hardikl Date: Thu, 27 Jun 2024 14:03:32 +0530 Subject: [PATCH 12/14] feat: using private cli with endpoint in rest --- .../rest/plugins/certificate/certificate.go | 2 +- conf/rest/9.12.0/security_certificate.yaml | 21 ++++++++++++------- grafana/dashboards/cmode/security.json | 17 +++++++-------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index e0db1fafb..1618e6fe2 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -96,7 +96,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, scope := certificateInstance.GetLabel("scope") CertType := certificateInstance.GetLabel("type") - if expiryTimeMetric = data.GetMetric("expiry_time"); expiryTimeMetric == nil { + if expiryTimeMetric = data.GetMetric("expiration"); expiryTimeMetric == nil { my.Logger.Error().Stack().Msg("missing expiry time metric") continue } diff --git a/conf/rest/9.12.0/security_certificate.yaml b/conf/rest/9.12.0/security_certificate.yaml index 4491f153a..fe793456f 100644 --- a/conf/rest/9.12.0/security_certificate.yaml +++ b/conf/rest/9.12.0/security_certificate.yaml @@ -1,17 +1,22 @@ name: SecurityCert -query: api/security/certificates +query: api/private/cli/security/certificate object: security_certificate counters: - ^^uuid - - ^name - - ^public_certificate => certificatePEM - - ^scope => scope - - ^serial_number => serial_number - - ^svm.name => svm + - ^cert_name => name + - ^public_cert => certificatePEM + - ^serial => serial_number - ^type => type - - expiry_time(timestamp) => expiry_time + - ^vserver => svm + - expiration(timestamp) => expiry_time + +#endpoints: +# - query: api/security/certificates +# counters: +# - ^^uuid +# - ^scope => scope plugins: - Certificate: @@ -26,7 +31,7 @@ export_options: - certificateIssuerType - expiry_time - name - - scope +# - scope - serial_number - svm - type diff --git a/grafana/dashboards/cmode/security.json b/grafana/dashboards/cmode/security.json index 3f0619aa3..a869c5f4c 100644 --- a/grafana/dashboards/cmode/security.json +++ b/grafana/dashboards/cmode/security.json @@ -1902,7 +1902,7 @@ }, { "datasource": "${DS_PROMETHEUS}", - "description": "This panel requires Harvest REST collector.\n\nThis panel displays Certificate expiration time.", + "description": "This panel displays SSL Certificate expiration time.", "fieldConfig": { "defaults": { "color": { @@ -2146,7 +2146,6 @@ "cluster", "datacenter", "expiry_time", - "scope", "svm", "type", "Value #B", @@ -2178,15 +2177,14 @@ }, "includeByName": {}, "indexByName": { - "Expiry Date": 6, - "Value #B": 7, + "Expiry Date": 5, + "Value #B": 6, "cluster": 1, - "cluster 2": 9, + "cluster 2": 8, "datacenter": 0, - "datacenter 2": 10, - "expiry_time": 8, - "name": 5, - "scope": 4, + "datacenter 2": 9, + "expiry_time": 7, + "name": 4, "svm": 2, "type": 3 }, @@ -2195,7 +2193,6 @@ "Value #B": "Expiry", "expiry_time": "Expires In", "name": "Certificate Name", - "scope": "Scope", "type": "Type" } } From e7590ff71ef9bd7b15c3bb360cca912a04da34ed Mon Sep 17 00:00:00 2001 From: hardikl Date: Thu, 27 Jun 2024 14:11:39 +0530 Subject: [PATCH 13/14] feat: using private cli with endpoint in rest --- cmd/collectors/rest/plugins/certificate/certificate.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 1618e6fe2..6eb568319 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -93,7 +93,6 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, continue } serialNumber := certificateInstance.GetLabel("serial_number") - scope := certificateInstance.GetLabel("scope") CertType := certificateInstance.GetLabel("type") if expiryTimeMetric = data.GetMetric("expiration"); expiryTimeMetric == nil { @@ -110,9 +109,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, unixTime = time.Now() } - if serialNumber == adminVserverSerial && scope == "cluster" && CertType == "server" { - // Admin SVM certificate is cluster scoped, but the REST API does not return the SVM name in its response. Add here for ZAPI parity - certificateInstance.SetLabel("svm", adminVserver) + if serialNumber == adminVserverSerial && CertType == "server" { my.setCertificateIssuerType(certificateInstance) my.setCertificateValidity(unixTime, certificateInstance) } From 2bc1d5490626f456864ef83344351d73d5cee81e Mon Sep 17 00:00:00 2001 From: hardikl Date: Fri, 28 Jun 2024 13:57:32 +0530 Subject: [PATCH 14/14] feat: minor review comment --- cmd/collectors/rest/plugins/certificate/certificate.go | 4 ++-- cmd/collectors/zapi/plugins/certificate/certificate.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/collectors/rest/plugins/certificate/certificate.go b/cmd/collectors/rest/plugins/certificate/certificate.go index 6eb568319..f34be4de3 100644 --- a/cmd/collectors/rest/plugins/certificate/certificate.go +++ b/cmd/collectors/rest/plugins/certificate/certificate.go @@ -93,7 +93,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, continue } serialNumber := certificateInstance.GetLabel("serial_number") - CertType := certificateInstance.GetLabel("type") + certType := certificateInstance.GetLabel("type") if expiryTimeMetric = data.GetMetric("expiration"); expiryTimeMetric == nil { my.Logger.Error().Stack().Msg("missing expiry time metric") @@ -109,7 +109,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, unixTime = time.Now() } - if serialNumber == adminVserverSerial && CertType == "server" { + if serialNumber == adminVserverSerial && certType == "server" { my.setCertificateIssuerType(certificateInstance) my.setCertificateValidity(unixTime, certificateInstance) } diff --git a/cmd/collectors/zapi/plugins/certificate/certificate.go b/cmd/collectors/zapi/plugins/certificate/certificate.go index 57c72096e..8a8684b28 100644 --- a/cmd/collectors/zapi/plugins/certificate/certificate.go +++ b/cmd/collectors/zapi/plugins/certificate/certificate.go @@ -106,7 +106,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, name := certificateInstance.GetLabel("name") serialNumber := certificateInstance.GetLabel("serial_number") svm := certificateInstance.GetLabel("svm") - CertType := certificateInstance.GetLabel("type") + certType := certificateInstance.GetLabel("type") certificateInstance.SetLabel("uuid", name+serialNumber+svm) if expiryTimeMetric = data.GetMetric("certificate-info.expiration-date"); expiryTimeMetric == nil { @@ -122,7 +122,7 @@ func (my *Certificate) Run(dataMap map[string]*matrix.Matrix) ([]*matrix.Matrix, unixTime = time.Now() } - if serialNumber == adminVserverSerial && CertType == "server" { + if serialNumber == adminVserverSerial && certType == "server" { my.setCertificateIssuerType(certificateInstance, certificateInstanceKey) my.setCertificateValidity(unixTime, certificateInstance) }