diff --git a/README.md b/README.md index 4f4b2cad..063a852d 100644 --- a/README.md +++ b/README.md @@ -161,13 +161,14 @@ Here are some examples of conditions you can use: #### Placeholders -| Placeholder | Description | Example of resolved value | -|:----------------------- |:------------------------------------------------------- |:------------------------- | -| `[STATUS]` | Resolves into the HTTP status of the request | 404 -| `[RESPONSE_TIME]` | Resolves into the response time the request took, in ms | 10 -| `[IP]` | Resolves into the IP of the target host | 192.168.0.232 -| `[BODY]` | Resolves into the response body. Supports JSONPath. | `{"name":"john.doe"}` -| `[CONNECTED]` | Resolves into whether a connection could be established | `true` +| Placeholder | Description | Example of resolved value | +|:-------------------------- |:--------------------------------------------------------------- |:------------------------- | +| `[STATUS]` | Resolves into the HTTP status of the request | 404 +| `[RESPONSE_TIME]` | Resolves into the response time the request took, in ms | 10 +| `[IP]` | Resolves into the IP of the target host | 192.168.0.232 +| `[BODY]` | Resolves into the response body. Supports JSONPath. | `{"name":"john.doe"}` +| `[CONNECTED]` | Resolves into whether a connection could be established | `true` +| `[CERTIFICATE_EXPIRATION]` | Resolves into the duration before certificate expiration, in ms | 4461677039, 0 (if not using HTTPS) #### Functions diff --git a/core/condition.go b/core/condition.go index ec2c7931..964416fd 100644 --- a/core/condition.go +++ b/core/condition.go @@ -35,6 +35,11 @@ const ( // Values that could replace the placeholder: true, false ConnectedPlaceHolder = "[CONNECTED]" + // CertificateExpirationPlaceholder is a placeholder for the duration before certificate expiration, in milliseconds. + // + // Values that could replace the placeholder: 4461677039 (~52 days) + CertificateExpirationPlaceholder = "[CERTIFICATE_EXPIRATION]" + // LengthFunctionPrefix is the prefix for the length function LengthFunctionPrefix = "len(" @@ -142,6 +147,8 @@ func sanitizeAndResolve(list []string, result *Result) []string { element = body case ConnectedPlaceHolder: element = strconv.FormatBool(result.Connected) + case CertificateExpirationPlaceholder: + element = strconv.FormatInt(int64(result.CertificateExpiration.Milliseconds()), 10) default: // if contains the BodyPlaceHolder, then evaluate json path if strings.Contains(element, BodyPlaceHolder) { @@ -174,11 +181,11 @@ func sanitizeAndResolve(list []string, result *Result) []string { return sanitizedList } -func sanitizeAndResolveNumerical(list []string, result *Result) []int { - var sanitizedNumbers []int +func sanitizeAndResolveNumerical(list []string, result *Result) []int64 { + var sanitizedNumbers []int64 sanitizedList := sanitizeAndResolve(list, result) for _, element := range sanitizedList { - if number, err := strconv.Atoi(element); err != nil { + if number, err := strconv.ParseInt(element, 10, 64); err != nil { // Default to 0 if the string couldn't be converted to an integer sanitizedNumbers = append(sanitizedNumbers, 0) } else { diff --git a/core/condition_test.go b/core/condition_test.go index 5ec95e25..d4e929df 100644 --- a/core/condition_test.go +++ b/core/condition_test.go @@ -1,6 +1,7 @@ package core import ( + "strconv" "testing" "time" ) @@ -309,3 +310,32 @@ func TestCondition_evaluateWithConnectedFailure(t *testing.T) { t.Errorf("Condition '%s' should have been a failure", condition) } } + +func TestCondition_evaluateWithUnsetCertificateExpiration(t *testing.T) { + condition := Condition("[CERTIFICATE_EXPIRATION] == 0") + result := &Result{} + condition.evaluate(result) + if !result.ConditionResults[0].Success { + t.Errorf("Condition '%s' should have been a success", condition) + } +} + +func TestCondition_evaluateWithCertificateExpirationGreaterThan(t *testing.T) { + acceptable := (time.Hour * 24 * 28).Milliseconds() + condition := Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt(acceptable, 10)) + result := &Result{CertificateExpiration: time.Hour * 24 * 60} + condition.evaluate(result) + if !result.ConditionResults[0].Success { + t.Errorf("Condition '%s' should have been a success", condition) + } +} + +func TestCondition_evaluateWithCertificateExpirationGreaterThanFailure(t *testing.T) { + acceptable := (time.Hour * 24 * 28).Milliseconds() + condition := Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt(acceptable, 10)) + result := &Result{CertificateExpiration: time.Hour * 24 * 14} + condition.evaluate(result) + if result.ConditionResults[0].Success { + t.Errorf("Condition '%s' should have been a failure", condition) + } +} diff --git a/core/service.go b/core/service.go index 54bccfa5..0a025d8a 100644 --- a/core/service.go +++ b/core/service.go @@ -169,6 +169,10 @@ func (service *Service) call(result *Result) { result.Errors = append(result.Errors, err.Error()) return } + if response.TLS != nil { + certificate := response.TLS.PeerCertificates[0] + result.CertificateExpiration = certificate.NotAfter.Sub(time.Now()) + } result.HTTPStatus = response.StatusCode result.Connected = response.StatusCode > 0 result.Body, err = ioutil.ReadAll(response.Body) diff --git a/core/types.go b/core/types.go index f17dbf3e..630c5df7 100644 --- a/core/types.go +++ b/core/types.go @@ -45,6 +45,9 @@ type Result struct { // Timestamp when the request was sent Timestamp time.Time `json:"timestamp"` + + // CertificateExpiration is the duration before the certificate expires + CertificateExpiration time.Duration `json:"certificate-expiration,omitempty"` } // ConditionResult result of a Condition