2023-03-15 16:00:52 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2022-11-16 14:27:56 +00:00
package healthcheck
import (
"crypto/x509"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/go-secure-stdlib/parseutil"
)
type CRLValidityPeriod struct {
Enabled bool
CRLExpiryPercentage int
DeltaCRLExpiryPercentage int
UnsupportedVersion bool
NoDeltas bool
CRLs map [ string ] * x509 . RevocationList
DeltaCRLs map [ string ] * x509 . RevocationList
CRLConfig * PathFetch
}
func NewCRLValidityPeriodCheck ( ) Check {
return & CRLValidityPeriod {
CRLs : make ( map [ string ] * x509 . RevocationList ) ,
DeltaCRLs : make ( map [ string ] * x509 . RevocationList ) ,
}
}
func ( h * CRLValidityPeriod ) Name ( ) string {
return "crl_validity_period"
}
func ( h * CRLValidityPeriod ) IsEnabled ( ) bool {
return h . Enabled
}
func ( h * CRLValidityPeriod ) DefaultConfig ( ) map [ string ] interface { } {
return map [ string ] interface { } {
"crl_expiry_pct_critical" : "95" ,
"delta_crl_expiry_pct_critical" : "95" ,
}
}
func ( h * CRLValidityPeriod ) LoadConfig ( config map [ string ] interface { } ) error {
value , err := parseutil . SafeParseIntRange ( config [ "crl_expiry_pct_critical" ] , 1 , 99 )
if err != nil {
return fmt . Errorf ( "error parsing %v.crl_expiry_pct_critical=%v: %w" , h . Name ( ) , config [ "crl_expiry_pct_critical" ] , err )
}
h . CRLExpiryPercentage = int ( value )
value , err = parseutil . SafeParseIntRange ( config [ "delta_crl_expiry_pct_critical" ] , 1 , 99 )
if err != nil {
return fmt . Errorf ( "error parsing %v.delta_crl_expiry_pct_critical=%v: %w" , h . Name ( ) , config [ "delta_crl_expiry_pct_critical" ] , err )
}
h . DeltaCRLExpiryPercentage = int ( value )
enabled , err := parseutil . ParseBool ( config [ "enabled" ] )
if err != nil {
return fmt . Errorf ( "error parsing %v.enabled: %w" , h . Name ( ) , err )
}
h . Enabled = enabled
return nil
}
func ( h * CRLValidityPeriod ) FetchResources ( e * Executor ) error {
2022-11-18 18:42:48 +00:00
exit , _ , issuers , err := pkiFetchIssuersList ( e , func ( ) {
2022-11-16 14:27:56 +00:00
h . UnsupportedVersion = true
} )
2022-11-17 14:26:13 +00:00
if exit || err != nil {
2022-11-16 14:27:56 +00:00
return err
}
for _ , issuer := range issuers {
exit , _ , crl , err := pkiFetchIssuerCRL ( e , issuer , false , func ( ) {
h . UnsupportedVersion = true
} )
2022-11-17 14:26:13 +00:00
if exit || err != nil {
2022-11-16 14:27:56 +00:00
if err != nil {
return err
}
continue
}
h . CRLs [ issuer ] = crl
exit , _ , delta , err := pkiFetchIssuerCRL ( e , issuer , true , func ( ) {
h . NoDeltas = true
} )
2022-11-17 14:26:13 +00:00
if exit || err != nil {
2022-11-16 14:27:56 +00:00
if err != nil {
return err
}
continue
}
h . DeltaCRLs [ issuer ] = delta
}
// Check if the issuer is fetched yet.
configRet , err := e . FetchIfNotFetched ( logical . ReadOperation , "/{{mount}}/config/crl" )
if err != nil {
return err
}
h . CRLConfig = configRet
return nil
}
func ( h * CRLValidityPeriod ) Evaluate ( e * Executor ) ( results [ ] * Result , err error ) {
if h . UnsupportedVersion {
ret := Result {
Status : ResultInvalidVersion ,
Endpoint : "/{{mount}}/issuers" ,
Message : "This health check requires Vault 1.11+ but an earlier version of Vault Server was contacted, preventing this health check from running." ,
}
return [ ] * Result { & ret } , nil
}
now := time . Now ( )
crlDisabled := false
if h . CRLConfig != nil {
if h . CRLConfig . IsSecretPermissionsError ( ) {
ret := Result {
Status : ResultInsufficientPermissions ,
Endpoint : "/{{mount}}/config/crl" ,
Message : "This prevents the health check from seeing if the CRL is disabled and dropping the severity of this check accordingly." ,
}
if e . Client . Token ( ) == "" {
ret . Message = "No token available so unable read authenticated CRL configuration for this mount. " + ret . Message
} else {
ret . Message = "This token lacks permission to read the CRL configuration for this mount. " + ret . Message
}
results = append ( results , & ret )
} else if h . CRLConfig . Secret != nil && h . CRLConfig . Secret . Data [ "disabled" ] != nil {
crlDisabled = h . CRLConfig . Secret . Data [ "disabled" ] . ( bool )
}
}
if h . NoDeltas && len ( h . DeltaCRLs ) == 0 {
ret := Result {
Status : ResultInvalidVersion ,
Endpoint : "/{{mount}}/issuer/*/crl/delta" ,
Message : "This health check validates Delta CRLs on Vault 1.12+, but an earlier version of Vault was used. No results about delta CRL validity will be returned." ,
}
results = append ( results , & ret )
}
for name , crl := range h . CRLs {
var ret Result
ret . Status = ResultOK
ret . Endpoint = "/{{mount}}/issuer/" + name + "/crl"
ret . Message = fmt . Sprintf ( "CRL's validity (%v to %v) is OK." , crl . ThisUpdate . Format ( "2006-01-02" ) , crl . NextUpdate . Format ( "2006-01-02" ) )
used := now . Sub ( crl . ThisUpdate )
total := crl . NextUpdate . Sub ( crl . ThisUpdate )
ratio := time . Duration ( ( int64 ( total ) * int64 ( h . CRLExpiryPercentage ) ) / int64 ( 100 ) )
if used >= ratio {
expWhen := crl . ThisUpdate . Add ( ratio )
ret . Status = ResultCritical
ret . Message = fmt . Sprintf ( "CRL's validity is outside of suggested rotation window: CRL's next update is expected at %v, but expires within %v%% of validity window (starting on %v and ending on %v). It is suggested to rotate this CRL and start propagating it to hosts to avoid any issues caused by stale CRLs." , crl . NextUpdate . Format ( "2006-01-02" ) , h . CRLExpiryPercentage , crl . ThisUpdate . Format ( "2006-01-02" ) , expWhen . Format ( "2006-01-02" ) )
2023-01-27 16:37:42 +00:00
if crlDisabled {
2022-11-16 14:27:56 +00:00
ret . Status = ResultInformational
ret . Message += " Because the CRL is disabled, this is less of a concern."
}
}
results = append ( results , & ret )
}
for name , crl := range h . DeltaCRLs {
var ret Result
ret . Status = ResultOK
ret . Endpoint = "/{{mount}}/issuer/" + name + "/crl/delta"
ret . Message = fmt . Sprintf ( "Delta CRL's validity (%v to %v) is OK." , crl . ThisUpdate . Format ( "2006-01-02" ) , crl . NextUpdate . Format ( "2006-01-02" ) )
used := now . Sub ( crl . ThisUpdate )
total := crl . NextUpdate . Sub ( crl . ThisUpdate )
ratio := time . Duration ( ( int64 ( total ) * int64 ( h . DeltaCRLExpiryPercentage ) ) / int64 ( 100 ) )
if used >= ratio {
expWhen := crl . ThisUpdate . Add ( ratio )
ret . Status = ResultCritical
ret . Message = fmt . Sprintf ( "Delta CRL's validity is outside of suggested rotation window: Delta CRL's next update is expected at %v, but expires within %v%% of validity window (starting on %v and ending on %v). It is suggested to rotate this Delta CRL and start propagating it to hosts to avoid any issues caused by stale CRLs." , crl . NextUpdate . Format ( "2006-01-02" ) , h . CRLExpiryPercentage , crl . ThisUpdate . Format ( "2006-01-02" ) , expWhen . Format ( "2006-01-02" ) )
2023-01-27 16:37:42 +00:00
if crlDisabled {
2022-11-16 14:27:56 +00:00
ret . Status = ResultInformational
ret . Message += " Because the CRL is disabled, this is less of a concern."
}
}
results = append ( results , & ret )
}
return
}