Skip to content

Commit

Permalink
feat: revoke certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez committed Jan 28, 2022
1 parent bc28159 commit e8a3241
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 11 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.17

require (
github.com/ettle/strcase v0.1.1
github.com/go-acme/lego/v4 v4.6.0
github.com/traefik/traefik/v2 v2.6.0
github.com/urfave/cli/v2 v2.3.0
)
Expand Down Expand Up @@ -38,7 +39,6 @@ require (
github.com/exoscale/egoscale v0.67.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/go-acme/lego/v4 v4.6.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
Expand Down
63 changes: 56 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"github.com/ettle/strcase"
"github.com/go-acme/lego/v4/lego"
"github.com/traefik/traefik/v2/pkg/provider/acme"
"github.com/urfave/cli/v2"
)
Expand All @@ -22,6 +23,7 @@ const (
flagDstFile = "dst"
flagDomain = "domain"
flagResolverName = "resolver-name"
flagRevoke = "revoke"
flagDryRun = "dry-run"
)

Expand All @@ -30,6 +32,7 @@ type configuration struct {
Destination string
Domain string
ResolverName string
Revoke bool
DryRun bool
}

Expand All @@ -39,6 +42,7 @@ func newConfiguration(cliCtx *cli.Context) configuration {
Destination: cliCtx.Path(flagDstFile),
Domain: cliCtx.String(flagDomain),
ResolverName: cliCtx.String(flagResolverName),
Revoke: cliCtx.Bool(flagRevoke),
DryRun: cliCtx.Bool(flagDryRun),
}
}
Expand Down Expand Up @@ -77,6 +81,12 @@ func main() {
EnvVars: []string{strcase.ToSNAKE(flagDomain)},
Value: "*",
},
&cli.BoolFlag{
Name: flagRevoke,
Usage: "Revoke certificates",
EnvVars: []string{strcase.ToSNAKE(flagRevoke)},
Value: false,
},
&cli.BoolFlag{
Name: flagDryRun,
Usage: "Dry run mode.",
Expand All @@ -85,7 +95,7 @@ func main() {
},
},
Action: func(cliCtx *cli.Context) error {
return run(newConfiguration(cliCtx))
return cleaner{configuration: newConfiguration(cliCtx)}.run()
},
}

Expand All @@ -95,23 +105,27 @@ func main() {
}
}

func run(config configuration) error {
type cleaner struct {
configuration
}

func (c cleaner) run() error {
data := map[string]*acme.StoredData{}
err := readJSONFile(config.Source, &data)
err := readJSONFile(c.Source, &data)
if err != nil {
return err
}

err = clean(config, data)
err = c.clean(c.configuration, data)
if err != nil {
return err
}

var encoder *json.Encoder
if config.DryRun {
if c.DryRun {
encoder = json.NewEncoder(os.Stdout)
} else {
output, err := os.Create(config.Destination)
output, err := os.Create(c.Destination)
if err != nil {
return err
}
Expand All @@ -124,23 +138,28 @@ func run(config configuration) error {
return encoder.Encode(data)
}

func clean(config configuration, data map[string]*acme.StoredData) error {
func (c cleaner) clean(config configuration, data map[string]*acme.StoredData) error {
for rName, storedData := range data {
if config.ResolverName != "*" && config.ResolverName != rName {
continue
}

if config.Domain == "*" {
c.revoke(storedData.Account, storedData.Certificates)
storedData.Certificates = make([]*acme.CertAndStore, 0)
continue
}

var keep []*acme.CertAndStore
var toRevoke []*acme.CertAndStore

for _, cert := range storedData.Certificates {
if strings.HasSuffix(cert.Domain.Main, config.Domain) || containsSuffixes(cert.Domain.SANs, config.Domain) {
toRevoke = append(toRevoke, cert)
continue
}
if strings.HasSuffix(cert.Certificate.Domain.Main, config.Domain) || containsSuffixes(cert.Certificate.Domain.SANs, config.Domain) {
toRevoke = append(toRevoke, cert)
continue
}

Expand All @@ -150,18 +169,48 @@ func clean(config configuration, data map[string]*acme.StoredData) error {
}

if strings.HasSuffix(certificate.Subject.CommonName, config.Domain) || containsSuffixes(certificate.DNSNames, config.Domain) {
toRevoke = append(toRevoke, cert)
continue
}

keep = append(keep, cert)
}

storedData.Certificates = keep

c.revoke(storedData.Account, toRevoke)
}

return nil
}

func (c cleaner) revoke(account *acme.Account, certificates []*acme.CertAndStore) {
if !c.Revoke {
return
}

if !c.DryRun {
log.Println("Revoke certificate")
return
}

config := lego.NewConfig(account)
config.CADirURL = lego.LEDirectoryProduction
config.UserAgent = "ldez-traefik-certs-cleaner"

client, err := lego.NewClient(config)
if err != nil {
log.Fatalf("Could not create client: %v", err)
}

for _, certificate := range certificates {
err := client.Certificate.Revoke(certificate.Certificate.Certificate)
if err != nil {
log.Printf("Failed to revoke certificate for %s: %v", certificate.Domain, err)
}
}
}

func containsSuffixes(domains []string, suffix string) bool {
for _, domain := range domains {
if strings.HasSuffix(domain, suffix) {
Expand Down
31 changes: 28 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ GLOBAL OPTIONS:
--dst value, -o value Path to the output of the acme.json file. (default: "./acme-new.json") [$DST]
--resolver-name value, -r value Name of the resolver. Use * to handle all resolvers. (default: "*") [$RESOLVER_NAME]
--domain value, -d value Domains to remove. Use * to remove all certificates. (default: "*") [$DOMAIN]
--revoke Revoke certificates (default: false) [$REVOKE]
--dry-run Dry run mode. (default: true) [$DRY_RUN]
--help, -h show help (default: false)
```
Expand Down Expand Up @@ -67,25 +68,49 @@ The content of the new file is displayed to the console output.

### Remove all certificates

Creates a new file `./acme-new.json`.

Remove certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --dry-run=false
```

Creates a new file `./acme-new.json`.
Remove and revoke certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --revoke --dry-run=false
```

### Remove all certificates for a Specific Resolver

Creates a new file `./acme-new.json`.

Remove certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --resolver-name=myresolver --dry-run=false
```

Creates a new file `./acme-new.json`.
Remove and revoke certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --resolver-name=myresolver --revoke --dry-run=false
```


### Remove the certificates of a Specific Domain

Creates a new file `./acme-new.json`.

Remove certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --domain=example.com --dry-run=false
```

Creates a new file `./acme-new.json`.
Remove and revoke certificates:

```console
$ traefik-certs-cleaner --src=./acme.json --domain=example.com --revoke --dry-run=false
```

0 comments on commit e8a3241

Please sign in to comment.