From 357c187f35819fff14e979240d33e9f975590304 Mon Sep 17 00:00:00 2001 From: "R.I.Pienaar" Date: Tue, 6 Aug 2024 10:57:33 +0300 Subject: [PATCH] Extract cli credential check into a reusable package Signed-off-by: R.I.Pienaar --- monitor/credentials.go | 80 ++++++++++++++++++++++++++++ monitor/credentials_test.go | 101 ++++++++++++++++++++++++++++++++++++ monitor/monitor.go | 21 ++++++++ 3 files changed, 202 insertions(+) create mode 100644 monitor/credentials.go create mode 100644 monitor/credentials_test.go diff --git a/monitor/credentials.go b/monitor/credentials.go new file mode 100644 index 00000000..f86aaa75 --- /dev/null +++ b/monitor/credentials.go @@ -0,0 +1,80 @@ +// Copyright 2024 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +import ( + "os" + "time" + + "github.com/nats-io/jwt/v2" + "github.com/nats-io/nkeys" +) + +type CredentialCheckOptions struct { + File string `json:"file" yaml:"file"` + ValidityWarning time.Duration `json:"validity_warning" yaml:"validity_warning"` + ValidityCritical time.Duration `json:"validity_critical" yaml:"validity_critical"` + RequiresExpiry bool `json:"requires_expiry" yaml:"requires_expiry"` +} + +func CheckCredential(check *Result, opts CredentialCheckOptions) error { + ok, err := fileAccessible(opts.File) + if err != nil { + check.Critical("credential not accessible: %v", err) + return nil + } + + if !ok { + check.Critical("credential not accessible") + return nil + } + + cb, err := os.ReadFile(opts.File) + if err != nil { + check.Critical("credential not accessible: %v", err) + return nil + } + + token, err := nkeys.ParseDecoratedJWT(cb) + if err != nil { + check.Critical("invalid credential: %v", err) + return nil + } + + claims, err := jwt.Decode(token) + if err != nil { + check.Critical("invalid credential: %v", err) + } + + now := time.Now().UTC().Unix() + cd := claims.Claims() + until := cd.Expires - now + crit := int64(opts.ValidityCritical.Seconds()) + warn := int64(opts.ValidityWarning.Seconds()) + + check.Pd(&PerfDataItem{Help: "Expiry time in seconds", Name: "expiry", Value: float64(until), Warn: float64(warn), Crit: float64(crit), Unit: "s"}) + + switch { + case cd.Expires == 0 && opts.RequiresExpiry: + check.Critical("never expires") + case opts.ValidityCritical > 0 && (until <= crit): + check.Critical("expires sooner than %s", f(opts.ValidityCritical)) + case opts.ValidityWarning > 0 && (until <= warn): + check.Warn("expires sooner than %s", f(opts.ValidityWarning)) + default: + check.Ok("expires in %s", time.Unix(cd.Expires, 0).UTC()) + } + + return nil +} diff --git a/monitor/credentials_test.go b/monitor/credentials_test.go new file mode 100644 index 00000000..39948404 --- /dev/null +++ b/monitor/credentials_test.go @@ -0,0 +1,101 @@ +// Copyright 2024 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +import ( + "os" + "testing" + "time" +) + +func TestCheckCredential(t *testing.T) { + noExpiry := `-----BEGIN NATS USER JWT----- +eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJBSUdIM0I2TEFGQkMzNktaSFJCSFI1QVZaTVFHQkdDS0NRTlNXRFBMN0U1NE5SM0I1SkxRIiwiaWF0IjoxNjk1MzY5NjU1LCJpc3MiOiJBRFFCT1haQTZaWk5MMko0VFpZNTZMUVpUN1FCVk9DNDVLVlQ3UDVNWkZVWU1LSVpaTUdaSE02QSIsIm5hbWUiOiJib2IiLCJzdWIiOiJVQkhPVDczREVGN1dZWUZUS1ZVSDZNWDNFUUVZSlFWWUNBRUJXUFJaSDNYR0E2WDdLRDNGUkFYSCIsIm5hdHMiOnsicHViIjp7fSwic3ViIjp7fSwic3VicyI6LTEsImRhdGEiOi0xLCJwYXlsb2FkIjotMSwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfX0.kGsxvI3NNNp60unItd-Eo1Yw6B9T3rBOeq7lvRY_klP5yTaBZwhCTKUNYdr_n2HNkCNB44fyW2_pmBhDki_CDQ +------END NATS USER JWT------ + +************************* IMPORTANT ************************* +NKEY Seed printed below can be used to sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN USER NKEY SEED----- +SUAIQJDZJGYOJN4NBOLYRRENCNTPXZ7PPVQW7RWEXWJUNBAFDRPDO27JWA +------END USER NKEY SEED------ + +*************************************************************` + + expires2100 := `-----BEGIN NATS USER JWT----- +eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJleHAiOjQxMDI0NDQ4MDAsImp0aSI6IlhRQkNTUUo3M0c3STRWR0JVUUNNQjdKRVlDWlVNUzdLUzJPU0Q1Skk3WjY0NEE0TU40SUEiLCJpYXQiOjE2OTUzNzA4OTcsImlzcyI6IkFERU5CTlBZSUwzTklXVkxCMjJVUU5FR0NMREhGSllNSUxEVEFQSlk1SFlQV05LQVZQNzJXREFSIiwibmFtZSI6ImJvYiIsInN1YiI6IlVCTTdYREtRUzRRQVBKUEFCSllWSU5RR1lETko2R043MjZNQ01DV0VZRDJTTU9GQVZOQ1E1M09IIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.3ytewtkFoRLKNeRJjPGOyNWeeQKqKdfHmyRL2ofaUiqj_OoN2LAmg_Ms2zpU-A_2xAiUH7VsMIRJxw1cx3bwAg +------END NATS USER JWT------ + +************************* IMPORTANT ************************* +NKEY Seed printed below can be used to sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN USER NKEY SEED----- +SUAKYITMHPMSYUGPNQBLLPGOPFQN44XNCGXHNSHLJJVMD3IKYGBOLAI7TI +------END USER NKEY SEED------ + +*************************************************************` + + writeCred := func(t *testing.T, cred string) string { + tf, err := os.CreateTemp(t.TempDir(), "") + assertNoError(t, err) + + tf.Write([]byte(cred)) + tf.Close() + + return tf.Name() + } + + t.Run("no expiry", func(t *testing.T) { + opts := CredentialCheckOptions{ + File: writeCred(t, noExpiry), + RequiresExpiry: true, + } + + check := &Result{} + assertNoError(t, CheckCredential(check, opts)) + assertListEquals(t, check.Criticals, "never expires") + assertListIsEmpty(t, check.Warnings) + + opts.File = writeCred(t, expires2100) + check = &Result{} + assertNoError(t, CheckCredential(check, opts)) + assertListIsEmpty(t, check.Criticals) + assertListIsEmpty(t, check.Warnings) + assertListEquals(t, check.OKs, "expires in 2100-01-01 00:00:00 +0000 UTC") + }) + + t.Run("critical", func(t *testing.T) { + check := &Result{} + assertNoError(t, CheckCredential(check, CredentialCheckOptions{ + File: writeCred(t, noExpiry), + ValidityCritical: 100 * 24 * 365 * time.Hour, + })) + assertListEquals(t, check.Criticals, "expires sooner than 100y0d0h0m0s") + assertListIsEmpty(t, check.Warnings) + assertListIsEmpty(t, check.OKs) + }) + + t.Run("warning", func(t *testing.T) { + check := &Result{} + assertNoError(t, CheckCredential(check, CredentialCheckOptions{ + File: writeCred(t, noExpiry), + ValidityWarning: 100 * 24 * 365 * time.Hour, + })) + assertListEquals(t, check.Warnings, "expires sooner than 100y0d0h0m0s") + assertListIsEmpty(t, check.Criticals) + assertListIsEmpty(t, check.OKs) + }) +} diff --git a/monitor/monitor.go b/monitor/monitor.go index 45617c9e..b3a9a43c 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -14,7 +14,9 @@ package monitor import ( + "fmt" "math/rand" + "os" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" @@ -53,3 +55,22 @@ func randomPassword(length int) string { return string(b) } + +func fileAccessible(f string) (bool, error) { + stat, err := os.Stat(f) + if err != nil { + return false, err + } + + if stat.IsDir() { + return false, fmt.Errorf("is a directory") + } + + file, err := os.Open(f) + if err != nil { + return false, err + } + file.Close() + + return true, nil +}