github.com/philhug/dnscontrol@v0.2.4-0.20180625181521-921fa9849001/commands/getCerts.go (about) 1 package commands 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "regexp" 8 "strings" 9 10 "github.com/StackExchange/dnscontrol/models" 11 "github.com/StackExchange/dnscontrol/pkg/acme" 12 "github.com/StackExchange/dnscontrol/pkg/normalize" 13 "github.com/urfave/cli" 14 ) 15 16 var _ = cmd(catUtils, func() *cli.Command { 17 var args GetCertsArgs 18 return &cli.Command{ 19 Name: "get-certs", 20 Usage: "Issue certificates via Let's Encrypt", 21 Action: func(c *cli.Context) error { 22 return exit(GetCerts(args)) 23 }, 24 Flags: args.flags(), 25 } 26 }()) 27 28 type GetCertsArgs struct { 29 GetDNSConfigArgs 30 GetCredentialsArgs 31 32 ACMEServer string 33 CertsFile string 34 RenewUnderDays int 35 CertDirectory string 36 Email string 37 AgreeTOS bool 38 Verbose bool 39 40 IgnoredProviders string 41 } 42 43 func (args *GetCertsArgs) flags() []cli.Flag { 44 flags := args.GetDNSConfigArgs.flags() 45 flags = append(flags, args.GetCredentialsArgs.flags()...) 46 47 flags = append(flags, cli.StringFlag{ 48 Name: "acme", 49 Destination: &args.ACMEServer, 50 Value: "live", 51 Usage: `ACME server to issue against. Give full directory endpoint. Can also use 'staging' or 'live' for standard Let's Encrpyt endpoints.`, 52 }) 53 flags = append(flags, cli.IntFlag{ 54 Name: "renew", 55 Destination: &args.RenewUnderDays, 56 Value: 15, 57 Usage: `Renew certs with less than this many days remaining`, 58 }) 59 flags = append(flags, cli.StringFlag{ 60 Name: "dir", 61 Destination: &args.CertDirectory, 62 Value: ".", 63 Usage: `Directory to store certificates and other data`, 64 }) 65 flags = append(flags, cli.StringFlag{ 66 Name: "certConfig", 67 Destination: &args.CertsFile, 68 Value: "certs.json", 69 Usage: `Json file containing list of certificates to issue`, 70 }) 71 flags = append(flags, cli.StringFlag{ 72 Name: "email", 73 Destination: &args.Email, 74 Value: "", 75 Usage: `Email to register with let's encrypt`, 76 }) 77 flags = append(flags, cli.BoolFlag{ 78 Name: "agreeTOS", 79 Destination: &args.AgreeTOS, 80 Usage: `Must provide this to agree to Let's Encrypt terms of service`, 81 }) 82 flags = append(flags, cli.StringFlag{ 83 Name: "skip", 84 Destination: &args.IgnoredProviders, 85 Value: "", 86 Usage: `Provider names to not use for challenges (comma separated)`, 87 }) 88 flags = append(flags, cli.BoolFlag{ 89 Name: "verbose", 90 Destination: &args.Verbose, 91 Usage: "Enable detailed logging from acme library", 92 }) 93 return flags 94 } 95 96 func GetCerts(args GetCertsArgs) error { 97 fmt.Println(args.JSFile) 98 // check agree flag 99 if !args.AgreeTOS { 100 return fmt.Errorf("You must agree to the Let's Encrypt Terms of Service by using -agreeTOS") 101 } 102 if args.Email == "" { 103 return fmt.Errorf("Must provide email to use for Let's Encrypt registration") 104 } 105 106 // load dns config 107 cfg, err := GetDNSConfig(args.GetDNSConfigArgs) 108 if err != nil { 109 return err 110 } 111 errs := normalize.NormalizeAndValidateConfig(cfg) 112 if PrintValidationErrors(errs) { 113 return fmt.Errorf("Exiting due to validation errors") 114 } 115 _, err = InitializeProviders(args.CredsFile, cfg, false) 116 if err != nil { 117 return err 118 } 119 120 for _, skip := range strings.Split(args.IgnoredProviders, ",") { 121 acme.IgnoredProviders[skip] = true 122 } 123 124 // load cert list 125 certList := []*acme.CertConfig{} 126 f, err := os.Open(args.CertsFile) 127 if err != nil { 128 return err 129 } 130 defer f.Close() 131 dec := json.NewDecoder(f) 132 err = dec.Decode(&certList) 133 if err != nil { 134 return err 135 } 136 if len(certList) == 0 { 137 return fmt.Errorf("Must provide at least one certificate to issue in cert configuration") 138 } 139 if err = validateCertificateList(certList, cfg); err != nil { 140 return err 141 } 142 acmeServer := args.ACMEServer 143 if acmeServer == "live" { 144 acmeServer = acme.LetsEncryptLive 145 } else if acmeServer == "staging" { 146 acmeServer = acme.LetsEncryptStage 147 } 148 client, err := acme.New(cfg, args.CertDirectory, args.Email, acmeServer) 149 if err != nil { 150 return err 151 } 152 for _, cert := range certList { 153 _, err := client.IssueOrRenewCert(cert, args.RenewUnderDays, args.Verbose) 154 if err != nil { 155 return err 156 } 157 } 158 return nil 159 } 160 161 var validCertNamesRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_\-]*$`) 162 163 func validateCertificateList(certs []*acme.CertConfig, cfg *models.DNSConfig) error { 164 for _, cert := range certs { 165 name := cert.CertName 166 if !validCertNamesRegex.MatchString(name) { 167 return fmt.Errorf("'%s' is not a valud certificate name. Only alphanumerics, - and _ allowed", name) 168 } 169 sans := cert.Names 170 if len(sans) > 100 { 171 return fmt.Errorf("certificate '%s' has too many SANs. Max of 100", name) 172 } 173 if len(sans) == 0 { 174 return fmt.Errorf("certificate '%s' needs at least one SAN", name) 175 } 176 for _, san := range sans { 177 d := cfg.DomainContainingFQDN(san) 178 if d == nil { 179 return fmt.Errorf("DNS config has no domain that matches SAN '%s'", san) 180 } 181 } 182 } 183 return nil 184 }