diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 18707e304..835b6a57c 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -56,20 +56,26 @@ func createRenew() *cli.Command { func renew(ctx context.Context, cmd *cli.Command) error { accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd)) if err != nil { - log.Fatal("Accounts storage initialization", log.ErrorAttr(err)) + return fmt.Errorf("accounts storage initialization: %w", err) } - keyType := getKeyType(cmd) + keyType, err := getKeyType(cmd.String(flgKeyType)) + if err != nil { + return fmt.Errorf("get the key type: %w", err) + } - account := setupAccount(ctx, keyType, accountsStorage) + account, err := setupAccount(ctx, keyType, accountsStorage) + if err != nil { + return fmt.Errorf("set up account: %w", err) + } if account.Registration == nil { - log.Fatal("The account is not registered. Use 'run' to register a new account.", slog.String("email", account.Email)) + return fmt.Errorf("the account %s is not registered", account.Email) } certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd)) if err != nil { - log.Fatal("Certificates storage", log.ErrorAttr(err)) + return fmt.Errorf("certificates storage initialization: %w", err) } meta := map[string]string{ @@ -94,10 +100,7 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc // as web servers would not be able to work with a combined file. certificates, err := certsStorage.ReadCertificate(domain, storage.ExtCert) if err != nil { - log.Fatal("Error while loading the certificate.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("error while reading the certificate for domain %q: %w", domain, err) } cert := certificates[0] @@ -110,7 +113,10 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc var client *lego.Client if !cmd.Bool(flgARIDisable) { - client = setupClient(cmd, account, keyType) + client, err = setupClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("set up client: %w", err) + } willingToSleep := cmd.Duration(flgARIWaitToRenewDuration) @@ -125,13 +131,14 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc slog.Duration("sleep", ariRenewalTime.Sub(now)), slog.Time("renewalTime", *ariRenewalTime), ) + time.Sleep(ariRenewalTime.Sub(now)) } } replacesCertID, err = certificate.MakeARICertID(cert) if err != nil { - log.Fatal("Error while construction the ARI CertID.", log.DomainAttr(domain), log.ErrorAttr(err)) + return fmt.Errorf("error while constructing the ARI CertID for domain %q: %w", domain, err) } } @@ -145,11 +152,15 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc } if client == nil { - client = setupClient(cmd, account, keyType) + client, err = setupClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("set up client: %w", err) + } } // This is just meant to be informal for the user. timeLeft := cert.NotAfter.Sub(time.Now().UTC()) + log.Info("acme: Trying renewal.", log.DomainAttr(domain), slog.Int("hoursRemaining", int(timeLeft.Hours())), @@ -160,15 +171,12 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc if cmd.Bool(flgReuseKey) { keyBytes, errR := certsStorage.ReadFile(domain, storage.ExtKey) if errR != nil { - log.Fatal("Error while loading the private key.", - log.DomainAttr(domain), - log.ErrorAttr(errR), - ) + return fmt.Errorf("error while reading the private key for domain %q: %w", domain, errR) } privateKey, errR = certcrypto.ParsePEMPrivateKey(keyBytes) if errR != nil { - return errR + return fmt.Errorf("error while parsing the private key for domain %q: %w", domain, errR) } } @@ -200,12 +208,15 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc certRes, err := client.Certificate.Obtain(ctx, request) if err != nil { - log.Fatal("Could not obtain the certificate.", log.ErrorAttr(err)) + return fmt.Errorf("could not obtain the certificate for domain %q: %w", domain, err) } certRes.Domain = domain - certsStorage.SaveResource(certRes) + err = certsStorage.SaveResource(certRes) + if err != nil { + return fmt.Errorf("could not save the resource: %w", err) + } hook.AddPathToMetadata(meta, certRes.Domain, certRes, certsStorage) @@ -215,15 +226,12 @@ func renewForDomains(ctx context.Context, cmd *cli.Command, account *storage.Acc func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType, certsStorage *storage.CertificatesStorage, meta map[string]string) error { csr, err := readCSRFile(cmd.String(flgCSR)) if err != nil { - log.Fatal("Could not read CSR file.", - slog.String(flgCSR, cmd.String(flgCSR)), - log.ErrorAttr(err), - ) + return fmt.Errorf("could not read CSR file %q: %w", cmd.String(flgCSR), err) } domain, err := certcrypto.GetCSRMainDomain(csr) if err != nil { - log.Fatal("Could not get CSR main domain.", log.ErrorAttr(err)) + return fmt.Errorf("could not get CSR main domain: %w", err) } // load the cert resource from files. @@ -231,10 +239,7 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account // as web servers would not be able to work with a combined file. certificates, err := certsStorage.ReadCertificate(domain, storage.ExtCert) if err != nil { - log.Fatal("Error while loading the certificate.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("error while reading the certificate for domain %q: %w", domain, err) } cert := certificates[0] @@ -247,7 +252,10 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account var client *lego.Client if !cmd.Bool(flgARIDisable) { - client = setupClient(cmd, account, keyType) + client, err = setupClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("set up client: %w", err) + } willingToSleep := cmd.Duration(flgARIWaitToRenewDuration) @@ -262,13 +270,14 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account slog.Duration("sleep", ariRenewalTime.Sub(now)), slog.Time("renewalTime", *ariRenewalTime), ) + time.Sleep(ariRenewalTime.Sub(now)) } } replacesCertID, err = certificate.MakeARICertID(cert) if err != nil { - log.Fatal("Error while construction the ARI CertID.", log.DomainAttr(domain), log.ErrorAttr(err)) + return fmt.Errorf("error while constructing the ARI CertID for domain %q: %w", domain, err) } } @@ -277,11 +286,15 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account } if client == nil { - client = setupClient(cmd, account, keyType) + client, err = setupClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("set up client: %w", err) + } } // This is just meant to be informal for the user. timeLeft := cert.NotAfter.Sub(time.Now().UTC()) + log.Info("acme: Trying renewal.", log.DomainAttr(domain), slog.Int("hoursRemaining", int(timeLeft.Hours())), @@ -295,10 +308,13 @@ func renewForCSR(ctx context.Context, cmd *cli.Command, account *storage.Account certRes, err := client.Certificate.ObtainForCSR(ctx, request) if err != nil { - log.Fatal("Could not obtain the certificate for CSR.", log.ErrorAttr(err)) + return fmt.Errorf("could not obtain the certificate for CSR: %w", err) } - certsStorage.SaveResource(certRes) + err = certsStorage.SaveResource(certRes) + if err != nil { + return fmt.Errorf("could not save the resource: %w", err) + } hook.AddPathToMetadata(meta, domain, certRes, certsStorage) diff --git a/cmd/cmd_revoke.go b/cmd/cmd_revoke.go index 2508f835b..de73ff926 100644 --- a/cmd/cmd_revoke.go +++ b/cmd/cmd_revoke.go @@ -2,9 +2,10 @@ package cmd import ( "context" - "log/slog" + "fmt" "github.com/go-acme/lego/v5/cmd/internal/storage" + "github.com/go-acme/lego/v5/lego" "github.com/go-acme/lego/v5/log" "github.com/urfave/cli/v3" ) @@ -21,56 +22,81 @@ func createRevoke() *cli.Command { func revoke(ctx context.Context, cmd *cli.Command) error { accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd)) if err != nil { - log.Fatal("Accounts storage initialization", log.ErrorAttr(err)) + return fmt.Errorf("accounts storage initialization: %w", err) } - keyType := getKeyType(cmd) + keyType, err := getKeyType(cmd.String(flgKeyType)) + if err != nil { + return fmt.Errorf("get the key type: %w", err) + } - account := setupAccount(ctx, keyType, accountsStorage) + account, err := setupAccount(ctx, keyType, accountsStorage) + if err != nil { + return fmt.Errorf("set up account: %w", err) + } if account.Registration == nil { - log.Fatal("Account is not registered. Use 'run' to register a new account.", slog.String("email", account.Email)) + return fmt.Errorf("the account %s is not registered", account.Email) } - client := newClient(cmd, account, keyType) + client, err := newClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("new client: %w", err) + } certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd)) if err != nil { - log.Fatal("Certificates storage", log.ErrorAttr(err)) + return fmt.Errorf("certificates storage initialization: %w", err) } - certsStorage.CreateRootFolder() + err = certsStorage.CreateRootFolder() + if err != nil { + return fmt.Errorf("root folder creation: %w", err) + } + + reason := cmd.Uint(flgReason) + keep := cmd.Bool(flgKeep) for _, domain := range cmd.StringSlice(flgDomains) { - log.Info("Trying to revoke the certificate.", log.DomainAttr(domain)) - - certBytes, err := certsStorage.ReadFile(domain, storage.ExtCert) - if err != nil { - log.Fatal("Error while revoking the certificate.", log.DomainAttr(domain), log.ErrorAttr(err)) - } - - reason := cmd.Uint(flgReason) - - err = client.Certificate.RevokeWithReason(ctx, certBytes, &reason) - if err != nil { - log.Fatal("Error while revoking the certificate.", log.DomainAttr(domain), log.ErrorAttr(err)) - } - - log.Info("Certificate was revoked.", log.DomainAttr(domain)) - - if cmd.Bool(flgKeep) { - return nil - } - - certsStorage.CreateArchiveFolder() - - err = certsStorage.MoveToArchive(domain) + err := revokeCertificate(ctx, client, certsStorage, domain, reason, keep) if err != nil { return err } - - log.Info("Certificate was archived", log.DomainAttr(domain)) } return nil } + +func revokeCertificate(ctx context.Context, client *lego.Client, certsStorage *storage.CertificatesStorage, domain string, reason uint, keep bool) error { + log.Info("Trying to revoke the certificate.", log.DomainAttr(domain)) + + certBytes, err := certsStorage.ReadFile(domain, storage.ExtCert) + if err != nil { + return fmt.Errorf("certificate reading for domain %s: %w", domain, err) + } + + err = client.Certificate.RevokeWithReason(ctx, certBytes, &reason) + if err != nil { + return fmt.Errorf("certificate revocation for domain %s: %w", domain, err) + } + + log.Info("The certificate has been revoked.", log.DomainAttr(domain)) + + if keep { + return nil + } + + err = certsStorage.CreateArchiveFolder() + if err != nil { + return fmt.Errorf("archive folder creation: %w", err) + } + + err = certsStorage.MoveToArchive(domain) + if err != nil { + return err + } + + log.Info("The certificate has been archived.", log.DomainAttr(domain)) + + return nil +} diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index ac7df1cde..a504a644a 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -41,26 +41,35 @@ func createRun() *cli.Command { func run(ctx context.Context, cmd *cli.Command) error { accountsStorage, err := storage.NewAccountsStorage(newAccountsStorageConfig(cmd)) if err != nil { - log.Fatal("Accounts storage initialization", log.ErrorAttr(err)) + return fmt.Errorf("accounts storage initialization: %w", err) } - keyType := getKeyType(cmd) + keyType, err := getKeyType(cmd.String(flgKeyType)) + if err != nil { + return fmt.Errorf("get the key type: %w", err) + } - account := setupAccount(ctx, keyType, accountsStorage) + account, err := setupAccount(ctx, keyType, accountsStorage) + if err != nil { + return fmt.Errorf("set up account: %w", err) + } - client := setupClient(cmd, account, keyType) + client, err := setupClient(cmd, account, keyType) + if err != nil { + return fmt.Errorf("set up client: %w", err) + } if account.Registration == nil { var reg *registration.Resource reg, err = registerAccount(ctx, cmd, client) if err != nil { - log.Fatal("Could not complete registration.", log.ErrorAttr(err)) + return fmt.Errorf("could not complete registration: %w", err) } account.Registration = reg if err = accountsStorage.Save(account); err != nil { - log.Fatal("Could not save the account file.", log.ErrorAttr(err)) + return fmt.Errorf("could not save the account file: %w", err) } fmt.Printf(rootPathWarningMessage, accountsStorage.GetRootPath()) @@ -68,19 +77,25 @@ func run(ctx context.Context, cmd *cli.Command) error { certsStorage, err := storage.NewCertificatesStorage(newCertificatesWriterConfig(cmd)) if err != nil { - log.Fatal("Certificates storage", log.ErrorAttr(err)) + return fmt.Errorf("certificates storage initialization: %w", err) } - certsStorage.CreateRootFolder() + err = certsStorage.CreateRootFolder() + if err != nil { + return fmt.Errorf("root folder creation: %w", err) + } cert, err := obtainCertificate(ctx, cmd, client) if err != nil { // 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. - log.Fatal("Could not obtain certificates", log.ErrorAttr(err)) + return fmt.Errorf("obtain certificate: %w", err) } - certsStorage.SaveResource(cert) + err = certsStorage.SaveResource(cert) + if err != nil { + return fmt.Errorf("could not save the resource: %w", err) + } meta := map[string]string{ hook.EnvAccountEmail: account.Email, diff --git a/cmd/internal/storage/accounts.go b/cmd/internal/storage/accounts.go index 39dad643e..93343e687 100644 --- a/cmd/internal/storage/accounts.go +++ b/cmd/internal/storage/accounts.go @@ -142,23 +142,17 @@ func (s *AccountsStorage) Save(account *Account) error { return os.WriteFile(s.accountFilePath, jsonBytes, filePerm) } -func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.PrivateKey) *Account { +func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.PrivateKey) (*Account, error) { fileBytes, err := os.ReadFile(s.accountFilePath) if err != nil { - log.Fatal("Could not load the account file.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not read the account file (userID: %s): %w", s.GetUserID(), err) } var account Account err = json.Unmarshal(fileBytes, &account) if err != nil { - log.Fatal("Could not parse the account file.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not parse the account file (userID: %s): %w", s.GetUserID(), err) } account.key = privateKey @@ -166,67 +160,52 @@ func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.Pri if account.Registration == nil || account.Registration.Body.Status == "" { reg, err := s.tryRecoverRegistration(ctx, privateKey) if err != nil { - log.Fatal("Could not load the account file. Registration is nil.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not load the account file, registration is nil (userID: %s): %w", s.GetUserID(), err) } account.Registration = reg err = s.Save(&account) if err != nil { - log.Fatal("Could not save the account file. Registration is nil.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not save the account file, registration is nil (userID: %s): %w", s.GetUserID(), err) } } - return &account + return &account, nil } -func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.PrivateKey { +func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) (crypto.PrivateKey, error) { accKeyPath := filepath.Join(s.keysPath, s.GetUserID()+".key") if _, err := os.Stat(accKeyPath); os.IsNotExist(err) { + // TODO(ldez): debug level? log.Info("No key found for the account. Generating a new private key.", slog.String("userID", s.GetUserID()), slog.Any("keyType", keyType), ) - s.createKeysFolder() + + err := CreateNonExistingFolder(s.keysPath) + if err != nil { + return nil, fmt.Errorf("could not check/create the directory %q for the account (userID: %s): %w", s.keysPath, s.GetUserID(), err) + } privateKey, err := generatePrivateKey(accKeyPath, keyType) if err != nil { - log.Fatal("Could not generate the RSA private account key.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not generate the private account key (userID: %s): %w", s.GetUserID(), err) } + // TODO(ldez): debug level? log.Info("Saved key.", slog.String("filepath", accKeyPath)) - return privateKey + return privateKey, nil } privateKey, err := LoadPrivateKey(accKeyPath) if err != nil { - log.Fatal("Could not load an RSA private key from the file.", - slog.String("filepath", accKeyPath), - log.ErrorAttr(err), - ) + return nil, fmt.Errorf("could not load the private key from the file %q: %w", accKeyPath, err) } - return privateKey -} - -func (s *AccountsStorage) createKeysFolder() { - if err := CreateNonExistingFolder(s.keysPath); err != nil { - log.Fatal("Could not check/create the directory for the account.", - slog.String("userID", s.GetUserID()), - log.ErrorAttr(err), - ) - } + return privateKey, nil } func (s *AccountsStorage) tryRecoverRegistration(ctx context.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) { diff --git a/cmd/internal/storage/accounts_test.go b/cmd/internal/storage/accounts_test.go index 9d2dfac0b..4f2c8e241 100644 --- a/cmd/internal/storage/accounts_test.go +++ b/cmd/internal/storage/accounts_test.go @@ -164,7 +164,8 @@ func TestAccountsStorage_LoadAccount(t *testing.T) { storage.accountFilePath = filepath.Join("testdata", accountFileName) - account := storage.LoadAccount(t.Context(), "") + account, err := storage.LoadAccount(t.Context(), "") + require.NoError(t, err) expected := &Account{ Email: "account@example.com", @@ -213,7 +214,8 @@ func TestAccountsStorage_GetPrivateKey(t *testing.T) { expectedPath := filepath.Join(test.basePath, baseAccountsRootFolderName, "test@example.com", baseKeysFolderName, "test@example.com.key") - privateKey := storage.GetPrivateKey(certcrypto.RSA4096) + privateKey, err := storage.GetPrivateKey(certcrypto.RSA4096) + require.NoError(t, err) assert.FileExists(t, expectedPath) diff --git a/cmd/internal/storage/certificates_reader.go b/cmd/internal/storage/certificates_reader.go index f602c73a3..add215007 100644 --- a/cmd/internal/storage/certificates_reader.go +++ b/cmd/internal/storage/certificates_reader.go @@ -3,6 +3,7 @@ package storage import ( "crypto/x509" "encoding/json" + "fmt" "log/slog" "os" "path/filepath" @@ -22,24 +23,18 @@ func NewCertificatesReader(basePath string) *CertificatesReader { } } -func (s *CertificatesReader) ReadResource(domain string) certificate.Resource { +func (s *CertificatesReader) ReadResource(domain string) (certificate.Resource, error) { raw, err := s.ReadFile(domain, ExtResource) if err != nil { - log.Fatal("Error while loading the metadata.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return certificate.Resource{}, fmt.Errorf("unable to load resource for domain %q: %w", domain, err) } var resource certificate.Resource if err = json.Unmarshal(raw, &resource); err != nil { - log.Fatal("Error while marshaling the metadata.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return certificate.Resource{}, fmt.Errorf("unable to unmarshal resource for domain %q: %w", domain, err) } - return resource + return resource, nil } func (s *CertificatesReader) ExistsFile(domain, extension string) bool { diff --git a/cmd/internal/storage/certificates_reader_test.go b/cmd/internal/storage/certificates_reader_test.go index 7dc6bc286..3db01c769 100644 --- a/cmd/internal/storage/certificates_reader_test.go +++ b/cmd/internal/storage/certificates_reader_test.go @@ -6,12 +6,14 @@ import ( "github.com/go-acme/lego/v5/certificate" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewCertificatesWriter_ReadResource(t *testing.T) { reader := NewCertificatesReader("testdata") - resource := reader.ReadResource("example.com") + resource, err := reader.ReadResource("example.com") + require.NoError(t, err) expected := certificate.Resource{ Domain: "example.com", diff --git a/cmd/internal/storage/certificates_writer.go b/cmd/internal/storage/certificates_writer.go index 2da85eceb..bca25e44b 100644 --- a/cmd/internal/storage/certificates_writer.go +++ b/cmd/internal/storage/certificates_writer.go @@ -75,46 +75,38 @@ func NewCertificatesWriter(config CertificatesWriterConfig) (*CertificatesWriter }, nil } -func (s *CertificatesWriter) CreateRootFolder() { +func (s *CertificatesWriter) CreateRootFolder() error { err := CreateNonExistingFolder(s.rootPath) if err != nil { - log.Fatal("Could not check/create the root folder", - slog.String("filepath", s.rootPath), - log.ErrorAttr(err), - ) + return fmt.Errorf("could not check/create the root folder %q: %w", s.rootPath, err) } + + return nil } -func (s *CertificatesWriter) CreateArchiveFolder() { +func (s *CertificatesWriter) CreateArchiveFolder() error { err := CreateNonExistingFolder(s.archivePath) if err != nil { - log.Fatal("Could not check/create the archive folder.", - slog.String("filepath", s.archivePath), - log.ErrorAttr(err), - ) + return fmt.Errorf("could not check/create the archive folder %q: %w", s.archivePath, err) } + + return nil } -func (s *CertificatesWriter) SaveResource(certRes *certificate.Resource) { +func (s *CertificatesWriter) SaveResource(certRes *certificate.Resource) error { domain := certRes.Domain // We store the certificate, private key and metadata in different files // as web servers would not be able to work with a combined file. err := s.writeFile(domain, ExtCert, certRes.Certificate) if err != nil { - log.Fatal("Unable to save Certificate.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("unable to save the certificate for the domain %q: %w", domain, err) } if certRes.IssuerCertificate != nil { err = s.writeFile(domain, ExtIssuer, certRes.IssuerCertificate) if err != nil { - log.Fatal("Unable to save IssuerCertificate.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("unable to save the issuer certificate for the domain %q: %w", domain, err) } } @@ -122,28 +114,24 @@ func (s *CertificatesWriter) SaveResource(certRes *certificate.Resource) { if certRes.PrivateKey != nil { err = s.writeCertificateFiles(domain, certRes) if err != nil { - log.Fatal("Unable to save PrivateKey.", log.DomainAttr(domain), log.ErrorAttr(err)) + return fmt.Errorf("unable to save the private key for the domain %q: %w", domain, err) } } else if s.pem || s.pfx { // we don't have the private key; can't write the .pem or .pfx file - log.Fatal("Unable to save PEM or PFX without the private key. Are you using a CSR?", log.DomainAttr(domain)) + return fmt.Errorf("unable to save PEM or PFX without the private key for the domain %q: probable usage of a CSR", domain) } jsonBytes, err := json.MarshalIndent(certRes, "", "\t") if err != nil { - log.Fatal("Unable to marshal CertResource.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("unable to marshal the resource for domain %q: %w", domain, err) } err = s.writeFile(domain, ExtResource, jsonBytes) if err != nil { - log.Fatal("Unable to save CertResource.", - log.DomainAttr(domain), - log.ErrorAttr(err), - ) + return fmt.Errorf("unable to save the resource for the domain %q: %w", domain, err) } + + return nil } func (s *CertificatesWriter) MoveToArchive(domain string) error { diff --git a/cmd/internal/storage/certificates_writer_test.go b/cmd/internal/storage/certificates_writer_test.go index 36db47fee..60f39a6f8 100644 --- a/cmd/internal/storage/certificates_writer_test.go +++ b/cmd/internal/storage/certificates_writer_test.go @@ -19,7 +19,8 @@ func TestCertificatesWriter_CreateRootFolder(t *testing.T) { require.NoDirExists(t, writer.rootPath) - writer.CreateRootFolder() + err = writer.CreateRootFolder() + require.NoError(t, err) require.DirExists(t, writer.rootPath) } @@ -32,7 +33,8 @@ func TestCertificatesWriter_CreateArchiveFolder(t *testing.T) { require.NoDirExists(t, writer.GetArchivePath()) - writer.CreateArchiveFolder() + err = writer.CreateArchiveFolder() + require.NoError(t, err) require.DirExists(t, writer.GetArchivePath()) } @@ -53,7 +55,7 @@ func TestCertificatesWriter_SaveResource(t *testing.T) { require.NoFileExists(t, filepath.Join(basePath, baseCertificatesFolderName, "example.com.key")) require.NoFileExists(t, filepath.Join(basePath, baseCertificatesFolderName, "example.com.json")) - writer.SaveResource(&certificate.Resource{ + err = writer.SaveResource(&certificate.Resource{ Domain: "example.com", CertURL: "https://acme.example.org/cert/123", CertStableURL: "https://acme.example.org/cert/456", @@ -62,6 +64,7 @@ func TestCertificatesWriter_SaveResource(t *testing.T) { IssuerCertificate: []byte("IssuerCertificate"), CSR: []byte("CSR"), }) + require.NoError(t, err) require.FileExists(t, filepath.Join(basePath, baseCertificatesFolderName, "example.com.crt")) require.FileExists(t, filepath.Join(basePath, baseCertificatesFolderName, "example.com.issuer.crt")) @@ -262,8 +265,11 @@ func setupCertificatesWriter(t *testing.T) *CertificatesWriter { ) require.NoError(t, err) - writer.CreateRootFolder() - writer.CreateArchiveFolder() + err = writer.CreateRootFolder() + require.NoError(t, err) + + err = writer.CreateArchiveFolder() + require.NoError(t, err) return writer } diff --git a/cmd/setup.go b/cmd/setup.go index b5a06bb5d..ca6ca808e 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -5,9 +5,9 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "errors" "fmt" "io" - "log/slog" "net/http" "os" "strings" @@ -24,35 +24,41 @@ import ( ) // setupClient creates a new client with challenge settings. -func setupClient(cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType) *lego.Client { - client := newClient(cmd, account, keyType) +func setupClient(cmd *cli.Command, account *storage.Account, keyType certcrypto.KeyType) (*lego.Client, error) { + client, err := newClient(cmd, account, keyType) + if err != nil { + return nil, fmt.Errorf("new client: %w", err) + } setupChallenges(cmd, client) - return client + return client, nil } -func setupAccount(ctx context.Context, keyType certcrypto.KeyType, accountsStorage *storage.AccountsStorage) *storage.Account { - privateKey := accountsStorage.GetPrivateKey(keyType) +func setupAccount(ctx context.Context, keyType certcrypto.KeyType, accountsStorage *storage.AccountsStorage) (*storage.Account, error) { + privateKey, err := accountsStorage.GetPrivateKey(keyType) + if err != nil { + return nil, fmt.Errorf("get private key: %w", err) + } if accountsStorage.ExistsAccountFilePath() { return accountsStorage.LoadAccount(ctx, privateKey) } - return storage.NewAccount(accountsStorage.GetEmail(), privateKey) + return storage.NewAccount(accountsStorage.GetEmail(), privateKey), nil } -func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) *lego.Client { +func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) (*lego.Client, error) { client, err := lego.NewClient(newClientConfig(cmd, acc, keyType)) if err != nil { - log.Fatal("Could not create client.", log.ErrorAttr(err)) + return nil, fmt.Errorf("new client: %w", err) } if client.GetExternalAccountRequired() && !cmd.IsSet(flgEAB) { // TODO(ldez): handle this flag. - log.Fatal(fmt.Sprintf("Server requires External Account Binding. Use --%s with --%s and --%s.", flgEAB, flgKID, flgHMAC)) + return nil, errors.New("server requires External Account Binding (EAB)") } - return client + return client, nil } func newClientConfig(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) *lego.Config { @@ -96,26 +102,25 @@ func newClientConfig(cmd *cli.Command, acc registration.User, keyType certcrypto } // getKeyType the type from which private keys should be generated. -func getKeyType(cmd *cli.Command) certcrypto.KeyType { - keyType := cmd.String(flgKeyType) +func getKeyType(keyType string) (certcrypto.KeyType, error) { switch strings.ToUpper(keyType) { case "RSA2048": - return certcrypto.RSA2048 + return certcrypto.RSA2048, nil case "RSA3072": - return certcrypto.RSA3072 + return certcrypto.RSA3072, nil case "RSA4096": - return certcrypto.RSA4096 + return certcrypto.RSA4096, nil case "RSA8192": - return certcrypto.RSA8192 + return certcrypto.RSA8192, nil case "EC256": - return certcrypto.EC256 + return certcrypto.EC256, nil case "EC384": - return certcrypto.EC384 + return certcrypto.EC384, nil } - log.Fatal("Unsupported KeyType.", slog.String("keyType", keyType)) + // TODO(ldez): log + fallback? - return "" + return "", fmt.Errorf("unsupported key type: %s", keyType) } func getUserAgent(cmd *cli.Command) string {