Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow API client to provide serial and detect duplicate entry error #1318

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion certdb/sql/database_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package sql
import (
"errors"
"fmt"
"regexp"
"time"

"github.com/cloudflare/cfssl/certdb"
cferr "github.com/cloudflare/cfssl/errors"
"github.com/go-sql-driver/mysql"
"github.com/mattn/go-sqlite3"

"github.com/jmoiron/sqlx"
"github.com/kisielk/sqlstruct"
Expand Down Expand Up @@ -76,7 +79,37 @@ var _ certdb.Accessor = &Accessor{}

func wrapSQLError(err error) error {
if err != nil {
return cferr.Wrap(cferr.CertStoreError, cferr.Unknown, err)

reason := cferr.Unknown

// Use detailed reason on unique constraint errors (i.e. will allow API client
// to detect already used cert serial in DB when API client is
// allowed to provide cert serial on cert singing). We don't detect this
// kind of problems by querying table for exisitng key before insert/update
// to avoid races. Unique constraint errors have different codes in different
// DB engines so must be detected separately.

// MySQL/MariaDB
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
reason = cferr.DuplicateEntry
}

// SQLite
var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) && (sqliteErr.Code == sqlite3.ErrConstraint) {

// Parsing error message is probably the only way to detect duplicate key
// errors in SQLite now...
if regexp.MustCompile(`(^|\s)UNIQUE constraint failed .*`).MatchString(err.Error()) {
reason = cferr.DuplicateEntry
}
}

// PostgresSQL
// TBD. See also: https://github.com/go-gorm/gorm/issues/4135

return cferr.Wrap(cferr.CertStoreError, reason, err)
}
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
ocspConfig "github.com/cloudflare/cfssl/ocsp/config"

// empty import of zlint/v3 required to have lints registered.
_ "github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
Expand Down Expand Up @@ -121,7 +122,7 @@ type SigningProfile struct {
CSRWhitelist *CSRWhitelist
NameWhitelist *regexp.Regexp
ExtensionWhitelist map[string]bool
ClientProvidesSerialNumbers bool
ClientProvidesSerialNumbers bool `json:"client_provides_serial_numbers"`
// LintRegistry is the collection of lints that should be used if
// LintErrLevel is configured. By default all ZLint lints are used. If
// ExcludeLints or ExcludeLintSources are set then this registry will be
Expand Down
3 changes: 3 additions & 0 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ const (
// RecordNotFound occurs when a SQL query targeting on one unique
// record failes to update the specified row in the table.
RecordNotFound
// DuplicateEntry occurs when SQL query tries to insert or update
// using key that must be unique in db table but already exists there.
DuplicateEntry
)

// The error interface implementation, which formats to a JSON object string.
Expand Down