This commit is contained in:
commit
91e551f6d5
12 changed files with 388 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/expiration-check
|
||||
/test.sh
|
||||
32
.woodpecker/build.yml
Normal file
32
.woodpecker/build.yml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
when:
|
||||
- event: [pull_request, tag]
|
||||
- event: push
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
- feature/*
|
||||
- release/*
|
||||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &golang_image 'golang:1.22'
|
||||
|
||||
steps:
|
||||
"Add vendor":
|
||||
image: *golang_image
|
||||
commands:
|
||||
- go mod vendor
|
||||
|
||||
"Run build":
|
||||
image: *golang_image
|
||||
commands:
|
||||
- go build
|
||||
|
||||
"Publish":
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitnet_api_key
|
||||
base_url: https://gitnet.fr
|
||||
files: ./expiration-check
|
||||
when:
|
||||
event: [tag]
|
||||
2
Makefile
Normal file
2
Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build:
|
||||
go build
|
||||
26
README.md
Normal file
26
README.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
Expiration checker
|
||||
==================
|
||||
|
||||
Checks the expiration dates of domains et certificates.
|
||||
|
||||
## Usage
|
||||
|
||||
Go to [releases](https://gitnet.fr/deblan/expiration-check/releases) and download the latest version.
|
||||
|
||||
```text
|
||||
$ expiration-check domains -d example.com -d other-example.com
|
||||
+-------------------+------+---------------------+
|
||||
| DOMAIN | DAYS | DATE |
|
||||
+-------------------+------+---------------------+
|
||||
| example.com | XX | YYYY-MM-DD HH:MM:SS |
|
||||
| other-example.com | XXX | YYYY-MM-DD HH:MM:SS |
|
||||
+-------------------+------+---------------------+
|
||||
|
||||
$ expiration-check certificates -d example.com -d other-example.com -d mail.example.com:993
|
||||
+-------------------+------+---------------------+
|
||||
| DOMAIN | DAYS | DATE |
|
||||
+-------------------+------+---------------------+
|
||||
| example.com | XX | YYYY-MM-DD HH:MM:SS |
|
||||
| other-example.com | XXX | YYYY-MM-DD HH:MM:SS |
|
||||
+-------------------+------+---------------------+
|
||||
```
|
||||
44
app.go
Normal file
44
app.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitnet.fr/deblan/expiration-check/checker"
|
||||
"gitnet.fr/deblan/expiration-check/render"
|
||||
)
|
||||
|
||||
func App() *cli.App {
|
||||
flags := []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "domain",
|
||||
Aliases: []string{"d"},
|
||||
Required: true,
|
||||
},
|
||||
}
|
||||
|
||||
return &cli.App{
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "certificate",
|
||||
Aliases: []string{"c", "cert", "certs", "certificates"},
|
||||
Usage: "Checks certificate",
|
||||
Flags: flags,
|
||||
Action: func(c *cli.Context) error {
|
||||
render.Render(checker.CheckCertificates(c.StringSlice("domain")), 30, 14)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "domain",
|
||||
Usage: "Checks domain expirations",
|
||||
Aliases: []string{"d", "domains"},
|
||||
Flags: flags,
|
||||
Action: func(c *cli.Context) error {
|
||||
render.Render(checker.CheckDomains(c.StringSlice("domain")), 30, 14)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
46
checker/certificates.go
Normal file
46
checker/certificates.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func FormatDomain(domain string) string {
|
||||
elements := strings.Split(domain, ":")
|
||||
|
||||
if len(elements) == 1 {
|
||||
return fmt.Sprintf("%s:443", elements[0])
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
|
||||
func CheckCertificate(domain string) Domain {
|
||||
now := time.Now()
|
||||
conn, err := tls.Dial("tcp", FormatDomain(domain), nil)
|
||||
|
||||
if err == nil {
|
||||
date := conn.ConnectionState().PeerCertificates[0].NotAfter
|
||||
daysLeft := date.Sub(now).Hours() / 24
|
||||
|
||||
return Domain{
|
||||
Name: domain,
|
||||
DaysLeft: math.Floor(daysLeft),
|
||||
Date: date.Format(time.DateTime),
|
||||
}
|
||||
}
|
||||
return Domain{Name: domain, Failed: true}
|
||||
}
|
||||
|
||||
func CheckCertificates(domains []string) []Domain {
|
||||
values := []Domain{}
|
||||
|
||||
for _, domain := range domains {
|
||||
values = append(values, CheckCertificate(domain))
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
91
checker/domains.go
Normal file
91
checker/domains.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RdapResponseData struct {
|
||||
Events []struct {
|
||||
EventAction string `json:"eventAction"`
|
||||
EventDate string `json:"eventDate"`
|
||||
} `json:"events"`
|
||||
}
|
||||
|
||||
type RdapServices struct {
|
||||
Services [][][]string `json:"services"`
|
||||
}
|
||||
|
||||
func GetRdapServices() map[string]string {
|
||||
response, _ := http.Get("https://data.iana.org/rdap/dns.json")
|
||||
values := make(map[string]string)
|
||||
|
||||
defer response.Body.Close()
|
||||
var data RdapServices
|
||||
json.NewDecoder(response.Body).Decode(&data)
|
||||
|
||||
for _, value := range data.Services {
|
||||
for _, tld := range value[0] {
|
||||
values[tld] = value[1][0]
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
func ExtractTld(domain string) string {
|
||||
elements := strings.Split(domain, ".")
|
||||
|
||||
if len(elements) == 1 {
|
||||
return elements[0]
|
||||
}
|
||||
|
||||
return strings.Join(elements[1:], ".")
|
||||
}
|
||||
|
||||
func RdapCheck(domain, service string) Domain {
|
||||
url := fmt.Sprintf("%sdomain/%s?jscard=1", service, domain)
|
||||
response, _ := http.Get(url)
|
||||
now := time.Now()
|
||||
|
||||
defer response.Body.Close()
|
||||
var data RdapResponseData
|
||||
json.NewDecoder(response.Body).Decode(&data)
|
||||
|
||||
for _, event := range data.Events {
|
||||
if event.EventAction == "expiration" {
|
||||
date, _ := time.Parse(time.RFC3339, event.EventDate)
|
||||
daysLeft := date.Sub(now).Hours() / 24
|
||||
|
||||
return Domain{
|
||||
Name: domain,
|
||||
DaysLeft: math.Floor(daysLeft),
|
||||
Date: date.Format(time.DateTime),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Domain{Name: domain, Failed: true}
|
||||
}
|
||||
|
||||
func CheckDomains(domains []string) []Domain {
|
||||
values := []Domain{}
|
||||
services := GetRdapServices()
|
||||
|
||||
for _, domain := range domains {
|
||||
tld := ExtractTld(domain)
|
||||
service := services[tld]
|
||||
|
||||
if service != "" {
|
||||
values = append(values, RdapCheck(domain, service))
|
||||
} else {
|
||||
values = append(values, Domain{Name: domain, Failed: true})
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
8
checker/struct.go
Normal file
8
checker/struct.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package checker
|
||||
|
||||
type Domain struct {
|
||||
Name string
|
||||
DaysLeft float64
|
||||
Date string
|
||||
Failed bool
|
||||
}
|
||||
22
go.mod
Normal file
22
go.mod
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
module gitnet.fr/deblan/expiration-check
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
)
|
||||
32
go.sum
Normal file
32
go.sum
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
|
||||
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
12
main.go
Normal file
12
main.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := App().Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
71
render/render.go
Normal file
71
render/render.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"gitnet.fr/deblan/expiration-check/checker"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func Render(values []checker.Domain, warning, danger float64) {
|
||||
sort.SliceStable(values, func(i, j int) bool {
|
||||
if values[i].Failed && values[j].Failed {
|
||||
return values[i].Name < values[j].Name
|
||||
}
|
||||
|
||||
if values[i].Failed {
|
||||
return false
|
||||
}
|
||||
|
||||
if values[j].Failed {
|
||||
return true
|
||||
}
|
||||
|
||||
return values[i].DaysLeft < values[j].DaysLeft
|
||||
})
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{
|
||||
text.Colors{0, text.FgCyan}.Sprint("Domain"),
|
||||
text.Colors{0, text.FgCyan}.Sprint("Days"),
|
||||
text.Colors{0, text.FgCyan}.Sprint("Date"),
|
||||
})
|
||||
t.SetColumnConfigs([]table.ColumnConfig{
|
||||
{Number: 2, Align: text.AlignRight},
|
||||
{Number: 3, Align: text.AlignRight},
|
||||
})
|
||||
|
||||
for _, value := range values {
|
||||
if value.Failed {
|
||||
t.AppendRow(table.Row{
|
||||
value.Name,
|
||||
text.Colors{0, text.FgRed}.Sprint("FAIL"),
|
||||
text.Colors{0, text.FgRed}.Sprint("FAIL"),
|
||||
}, table.RowConfig{})
|
||||
} else {
|
||||
var days string
|
||||
var date string
|
||||
|
||||
if value.DaysLeft <= danger {
|
||||
days = text.Colors{0, text.FgRed}.Sprint("FAIL")
|
||||
date = text.Colors{0, text.FgRed}.Sprint("FAIL")
|
||||
} else if value.DaysLeft <= warning {
|
||||
days = text.Colors{0, text.FgYellow}.Sprint(value.DaysLeft)
|
||||
date = text.Colors{0, text.FgYellow}.Sprint(value.Date)
|
||||
} else {
|
||||
days = text.Colors{0, text.FgGreen}.Sprint(value.DaysLeft)
|
||||
date = text.Colors{0, text.FgGreen}.Sprint(value.Date)
|
||||
}
|
||||
|
||||
t.AppendRow(table.Row{
|
||||
value.Name,
|
||||
days,
|
||||
date,
|
||||
}, table.RowConfig{})
|
||||
}
|
||||
}
|
||||
|
||||
t.Render()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue