func revoke(c *cli.Context) error { conf, _, client := setup(c) err := checkFolder(conf.CertPath()) if err != nil { logger().Fatalf("Could not check/create path: %s", err.Error()) } for _, domain := range c.GlobalStringSlice("domains") { logger().Printf("Trying to revoke certificate for domain %s", domain) certPath := path.Join(conf.CertPath(), domain+".crt") certBytes, err := ioutil.ReadFile(certPath) err = client.RevokeCertificate(certBytes) if err != nil { logger().Fatalf("Error while revoking the certificate for domain %s\n\t%s", domain, err.Error()) } else { logger().Print("Certificate was revoked.") } } return nil }
// Simple wrapper to add multiple resources func AutoAddResources(fileName string, keys []string, c *cli.Context) error { setStoreFormatFromFileName(fileName) config := util.Config{ IgnoreList: c.GlobalStringSlice("exclude-attr"), Timeout: int(c.Duration("timeout") / time.Millisecond), } var gossConfig GossConfig if _, err := os.Stat(fileName); err == nil { gossConfig = ReadJSON(fileName) } else { gossConfig = *NewGossConfig() } sys := system.New(c) for _, key := range keys { if err := AutoAddResource(fileName, gossConfig, key, c, config, sys); err != nil { return err } } WriteJSON(fileName, gossConfig) return nil }
// Populate updates the specified project context based on command line arguments and subcommands. func Populate(context *project.Context, c *cli.Context) { context.ComposeFiles = c.GlobalStringSlice("file") if len(context.ComposeFiles) == 0 { context.ComposeFiles = []string{"docker-compose.yml"} if _, err := os.Stat("docker-compose.override.yml"); err == nil { context.ComposeFiles = append(context.ComposeFiles, "docker-compose.override.yml") } } context.ProjectName = c.GlobalString("project-name") }
// Populate updates the specified project context based on command line arguments and subcommands. func Populate(context *project.Context, c *cli.Context) { // urfave/cli does not distinguish whether the first string in the slice comes from the envvar // or is from a flag. Worse off, it appends the flag values to the envvar value instead of // overriding it. To ensure the multifile envvar case is always handled, the first string // must always be split. It gives a more consistent behavior, then, to split each string in // the slice. for _, v := range c.GlobalStringSlice("file") { context.ComposeFiles = append(context.ComposeFiles, strings.Split(v, string(os.PathListSeparator))...) } if len(context.ComposeFiles) == 0 { context.ComposeFiles = []string{"docker-compose.yml"} if _, err := os.Stat("docker-compose.override.yml"); err == nil { context.ComposeFiles = append(context.ComposeFiles, "docker-compose.override.yml") } } context.ProjectName = c.GlobalString("project-name") }
func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { if c.GlobalIsSet("http-timeout") { acme.HTTPClient = http.Client{Timeout: time.Duration(c.GlobalInt("http-timeout")) * time.Second} } if c.GlobalIsSet("dns-timeout") { acme.DNSTimeout = time.Duration(c.GlobalInt("dns-timeout")) * time.Second } if len(c.GlobalStringSlice("dns-resolvers")) > 0 { resolvers := []string{} for _, resolver := range c.GlobalStringSlice("dns-resolvers") { if !strings.Contains(resolver, ":") { resolver += ":53" } resolvers = append(resolvers, resolver) } acme.RecursiveNameservers = resolvers } err := checkFolder(c.GlobalString("path")) if err != nil { logger().Fatalf("Could not check/create path: %s", err.Error()) } conf := NewConfiguration(c) if len(c.GlobalString("email")) == 0 { logger().Fatal("You have to pass an account (email address) to the program using --email or -m") } //TODO: move to account struct? Currently MUST pass email. acc := NewAccount(c.GlobalString("email"), conf) keyType, err := conf.KeyType() if err != nil { logger().Fatal(err.Error()) } client, err := acme.NewClient(c.GlobalString("server"), acc, keyType) if err != nil { logger().Fatalf("Could not create client: %s", err.Error()) } if len(c.GlobalStringSlice("exclude")) > 0 { client.ExcludeChallenges(conf.ExcludedSolvers()) } if c.GlobalIsSet("webroot") { provider, err := webroot.NewHTTPProvider(c.GlobalString("webroot")) if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.HTTP01, provider) // --webroot=foo indicates that the user specifically want to do a HTTP challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01}) } if c.GlobalIsSet("memcached-host") { provider, err := memcached.NewMemcachedProvider(c.GlobalStringSlice("memcached-host")) if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.HTTP01, provider) // --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01}) } if c.GlobalIsSet("http") { if strings.Index(c.GlobalString("http"), ":") == -1 { logger().Fatalf("The --http switch only accepts interface:port or :port for its argument.") } client.SetHTTPAddress(c.GlobalString("http")) } if c.GlobalIsSet("tls") { if strings.Index(c.GlobalString("tls"), ":") == -1 { logger().Fatalf("The --tls switch only accepts interface:port or :port for its argument.") } client.SetTLSAddress(c.GlobalString("tls")) } if c.GlobalIsSet("dns") { var err error var provider acme.ChallengeProvider switch c.GlobalString("dns") { case "azure": provider, err = azure.NewDNSProvider() case "auroradns": provider, err = auroradns.NewDNSProvider() case "cloudflare": provider, err = cloudflare.NewDNSProvider() case "digitalocean": provider, err = digitalocean.NewDNSProvider() case "dnsimple": provider, err = dnsimple.NewDNSProvider() case "dnsmadeeasy": provider, err = dnsmadeeasy.NewDNSProvider() case "exoscale": provider, err = exoscale.NewDNSProvider() case "dyn": provider, err = dyn.NewDNSProvider() case "gandi": provider, err = gandi.NewDNSProvider() case "gcloud": provider, err = googlecloud.NewDNSProvider() case "linode": provider, err = linode.NewDNSProvider() case "manual": provider, err = acme.NewDNSProviderManual() case "namecheap": provider, err = namecheap.NewDNSProvider() case "rackspace": provider, err = rackspace.NewDNSProvider() case "route53": provider, err = route53.NewDNSProvider() case "rfc2136": provider, err = rfc2136.NewDNSProvider() case "vultr": provider, err = vultr.NewDNSProvider() case "ovh": provider, err = ovh.NewDNSProvider() case "pdns": provider, err = pdns.NewDNSProvider() case "ns1": provider, err = ns1.NewDNSProvider() case "dnspod": provider, err = dnspod.NewDNSProvider() } if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.DNS01, provider) // --dns=foo indicates that the user specifically want to do a DNS challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01}) } return conf, acc, client }
func renew(c *cli.Context) error { conf, _, client := setup(c) if len(c.GlobalStringSlice("domains")) <= 0 { logger().Fatal("Please specify at least one domain.") } domain := c.GlobalStringSlice("domains")[0] // load the cert resource from files. // We store the certificate, private key and metadata in different files // as web servers would not be able to work with a combined file. certPath := path.Join(conf.CertPath(), domain+".crt") privPath := path.Join(conf.CertPath(), domain+".key") metaPath := path.Join(conf.CertPath(), domain+".json") certBytes, err := ioutil.ReadFile(certPath) if err != nil { logger().Fatalf("Error while loading the certificate for domain %s\n\t%s", domain, err.Error()) } if c.IsSet("days") { expTime, err := acme.GetPEMCertExpiration(certBytes) if err != nil { logger().Printf("Could not get Certification expiration for domain %s", domain) } if int(expTime.Sub(time.Now()).Hours()/24.0) > c.Int("days") { return nil } } metaBytes, err := ioutil.ReadFile(metaPath) if err != nil { logger().Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error()) } var certRes acme.CertificateResource err = json.Unmarshal(metaBytes, &certRes) if err != nil { logger().Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error()) } if c.Bool("reuse-key") { keyBytes, err := ioutil.ReadFile(privPath) if err != nil { logger().Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error()) } certRes.PrivateKey = keyBytes } certRes.Certificate = certBytes newCert, err := client.RenewCertificate(certRes, !c.Bool("no-bundle"), c.Bool("must-staple")) if err != nil { logger().Fatalf("%s", err.Error()) } saveCertRes(newCert, conf) return nil }
func run(c *cli.Context) error { conf, acc, client := setup(c) if acc.Registration == nil { reg, err := client.Register() if err != nil { logger().Fatalf("Could not complete registration\n\t%s", err.Error()) } acc.Registration = reg acc.Save() logger().Print("!!!! HEADS UP !!!!") logger().Printf(` Your account credentials have been saved in your Let's Encrypt configuration directory at "%s". You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained from Let's Encrypt so making regular backups of this folder is ideal.`, conf.AccountPath(c.GlobalString("email"))) } // If the agreement URL is empty, the account still needs to accept the LE TOS. if acc.Registration.Body.Agreement == "" { handleTOS(c, client, acc) } // we require either domains or csr, but not both hasDomains := len(c.GlobalStringSlice("domains")) > 0 hasCsr := len(c.GlobalString("csr")) > 0 if hasDomains && hasCsr { logger().Fatal("Please specify either --domains/-d or --csr/-c, but not both") } if !hasDomains && !hasCsr { logger().Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)") } var cert acme.CertificateResource var failures map[string]error if hasDomains { // obtain a certificate, generating a new private key cert, failures = client.ObtainCertificate(c.GlobalStringSlice("domains"), !c.Bool("no-bundle"), nil, c.Bool("must-staple")) } else { // read the CSR csr, err := readCSRFile(c.GlobalString("csr")) if err != nil { // we couldn't read the CSR failures = map[string]error{"csr": err} } else { // obtain a certificate for this CSR cert, failures = client.ObtainCertificateForCSR(*csr, !c.Bool("no-bundle")) } } if len(failures) > 0 { for k, v := range failures { logger().Printf("[%s] Could not obtain certificates\n\t%s", k, v.Error()) } // Make sure to return a non-zero exit code if ObtainSANCertificate // returned at least one error. Due to us not returning partial // certificate we can just exit here instead of at the end. os.Exit(1) } err := checkFolder(conf.CertPath()) if err != nil { logger().Fatalf("Could not check/create path: %s", err.Error()) } saveCertRes(cert, conf) return nil }
func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) { if c.GlobalIsSet("http-timeout") { acme.HTTPClient = http.Client{Timeout: time.Duration(c.GlobalInt("http-timeout")) * time.Second} } if c.GlobalIsSet("dns-timeout") { acme.DNSTimeout = time.Duration(c.GlobalInt("dns-timeout")) * time.Second } if len(c.GlobalStringSlice("dns-resolvers")) > 0 { resolvers := []string{} for _, resolver := range c.GlobalStringSlice("dns-resolvers") { if !strings.Contains(resolver, ":") { resolver += ":53" } resolvers = append(resolvers, resolver) } acme.RecursiveNameservers = resolvers } err := checkFolder(c.GlobalString("path")) if err != nil { logger().Fatalf("Could not check/create path: %s", err.Error()) } conf := NewConfiguration(c) if len(c.GlobalString("email")) == 0 { logger().Fatal("You have to pass an account (email address) to the program using --email or -m") } //TODO: move to account struct? Currently MUST pass email. acc := NewAccount(c.GlobalString("email"), conf) keyType, err := conf.KeyType() if err != nil { logger().Fatal(err.Error()) } client, err := acme.NewClient(c.GlobalString("server"), acc, keyType) if err != nil { logger().Fatalf("Could not create client: %s", err.Error()) } if len(c.GlobalStringSlice("exclude")) > 0 { client.ExcludeChallenges(conf.ExcludedSolvers()) } if c.GlobalIsSet("webroot") { provider, err := webroot.NewHTTPProvider(c.GlobalString("webroot")) if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.HTTP01, provider) // --webroot=foo indicates that the user specifically want to do a HTTP challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01}) } if c.GlobalIsSet("memcached-host") { provider, err := memcached.NewMemcachedProvider(c.GlobalStringSlice("memcached-host")) if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.HTTP01, provider) // --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.DNS01, acme.TLSSNI01}) } if c.GlobalIsSet("http") { if strings.Index(c.GlobalString("http"), ":") == -1 { logger().Fatalf("The --http switch only accepts interface:port or :port for its argument.") } client.SetHTTPAddress(c.GlobalString("http")) } if c.GlobalIsSet("tls") { if strings.Index(c.GlobalString("tls"), ":") == -1 { logger().Fatalf("The --tls switch only accepts interface:port or :port for its argument.") } client.SetTLSAddress(c.GlobalString("tls")) } if c.GlobalIsSet("dns") { provider, err := dns.NewDNSChallengeProviderByName(c.GlobalString("dns")) if err != nil { logger().Fatal(err) } client.SetChallengeProvider(acme.DNS01, provider) // --dns=foo indicates that the user specifically want to do a DNS challenge // infer that the user also wants to exclude all other challenges client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01}) } return conf, acc, client }
// // handleCommand is a generic wrapper for handling commands, or more precisely their errors // func handleCommand(cx *cli.Context, options []string, cmd *cliCommand, method func(*formatter, *cli.Context, *cliCommand) error) error { // step: handle any panics in the command defer func() { if r := recover(); r != nil { fmt.Fprintf(os.Stderr, "[error] internal error occurred, message: %s", r) os.Exit(1) } }() // step: check the required options were specified for _, k := range options { items := strings.Split(k, ":") if len(items) != 3 { panic("invalid required option definition, SCOPE:NAME:TYPE") } name := items[1] // // @Fix the cli lib IsSet does not check if the option was set by a environment variable, the // issue https://github.com/urfave/cli/issues/294 highlights problem. As a consequence, we can't determine // if the variable is actually set. The hack below attempts to remedy it. // var invalid bool switch scope := items[0]; scope { case "g": switch t := items[2]; t { case "s": invalid = !cx.GlobalIsSet(name) && cx.String(name) == "" case "a": invalid = !cx.GlobalIsSet(name) && len(cx.GlobalStringSlice(name)) == 0 } if invalid { printError("the global option: '%s' is required", name) } default: switch t := items[2]; t { case "s": invalid = !cx.IsSet(name) && cx.String(name) == "" case "a": invalid = !cx.IsSet(name) && len(cx.StringSlice(name)) == 0 } if invalid { printError("the command option: '%s' is required", name) } } } // step: create a cli output writer, err := newFormatter(cx.GlobalString("format"), os.Stdout) if err != nil { printError("error: %s", err) } // step: call the command and handle any errors if err := method(writer, cx, cmd); err != nil { printError("operation failed, error: %s", err) } return nil }