Skip to content

Commit

Permalink
Add repository for CSR
Browse files Browse the repository at this point in the history
Signed-off-by: nyagamunene <[email protected]>
  • Loading branch information
nyagamunene committed Nov 20, 2024
1 parent c40b718 commit cbaee01
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 26 deletions.
26 changes: 19 additions & 7 deletions certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ type CSRMetadata struct {
}

type CSR struct {
CSR []byte `json:"csr"`
PrivateKey []byte `json:"private_key"`
EntityID string `json:"entity_id"`
Status string `json:"status"`
SubmittedAt time.Time `json:"submitted_at"`
ProcessedAt time.Time `json:"processed_at"`
SerialNumber string `json:"serial_number"`
CSR []byte `json:"csr" db:"csr"`
PrivateKey []byte `json:"private_key" db:"private_key"`
EntityID string `json:"entity_id" db:"entity_id"`
Status string `json:"status" db:"status"`
SubmittedAt time.Time `json:"submitted_at" db:"submitted_at"`
ProcessedAt time.Time `json:"processed_at" db:"processed_at"`
SerialNumber string `json:"serial_number" db:"serial_number"`
}

type CSRPage struct {
PageMetadata
CSRs []CSR
}

type Service interface {
Expand Down Expand Up @@ -133,3 +138,10 @@ type Repository interface {
// RemoveCert deletes cert from database.
RemoveCert(ctx context.Context, entityId string) error
}

type CSRRepository interface {
CreateCSR(context.Context, CSR) error
UpdateCSR(context.Context, CSR) error
ListCSRs(context.Context, PageMetadata) (CSRPage, error)
RetrieveCSR(context.Context, string) (CSR, error)
}
6 changes: 4 additions & 2 deletions cmd/certs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import (
grpcserver "github.com/absmach/certs/internal/server/grpc"
httpserver "github.com/absmach/certs/internal/server/http"
"github.com/absmach/certs/internal/uuid"
cpostgres "github.com/absmach/certs/postgres"
cpostgres "github.com/absmach/certs/postgres/certs"
csrpostgres "github.com/absmach/certs/postgres/csr"
"github.com/absmach/certs/tracing"
"github.com/caarlos0/env/v10"
"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -146,7 +147,8 @@ func main() {
func newService(ctx context.Context, db *sqlx.DB, tracer trace.Tracer, logger *slog.Logger, dbConfig pgClient.Config, config *certs.Config) (certs.Service, error) {
database := postgres.NewDatabase(db, dbConfig, tracer)
repo := cpostgres.NewRepository(database)
svc, err := certs.NewService(ctx, repo, config)
csrRepo := csrpostgres.NewRepository(database)
svc, err := certs.NewService(ctx, repo, csrRepo, config)
if err != nil {
return nil, err
}
Expand Down
154 changes: 154 additions & 0 deletions postgres/csr/csr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package postgres

import (
"context"
"database/sql"
"fmt"

"github.com/absmach/certs"
"github.com/absmach/certs/errors"
"github.com/absmach/certs/internal/postgres"
"github.com/jackc/pgx/v5/pgconn"
)

// Postgres error codes:
// https://www.postgresql.org/docs/current/errcodes-appendix.html
const (
errDuplicate = "23505" // unique_violation
errTruncation = "22001" // string_data_right_truncation
errFK = "23503" // foreign_key_violation
errInvalid = "22P02" // invalid_text_representation
errUntranslatable = "22P05" // untranslatable_character
errInvalidChar = "22021" // character_not_in_repertoire
)

var (
ErrConflict = errors.New("entity already exists")
ErrMalformedEntity = errors.New("malformed entity")
ErrCreateEntity = errors.New("failed to create entity")
)

type CSRRepo struct {
db postgres.Database
}

func NewRepository(db postgres.Database) certs.CSRRepository {
return CSRRepo{
db: db,
}
}

func (repo CSRRepo) CreateCSR(ctx context.Context, cert certs.CSR) error {
q := `
INSERT INTO certs (serial_number, csr, private_key, entity_id, status, submitted_at, processed_at)
VALUES (:serial_number, :csr, :private_key, :entity_id, :status, :submitted_at, :processed_at)`
_, err := repo.db.NamedExecContext(ctx, q, cert)
if err != nil {
return handleError(certs.ErrCreateEntity, err)
}
return nil
}

func (repo CSRRepo) UpdateCSR(ctx context.Context, cert certs.CSR) error {
q := `UPDATE certs SET certificate = :certificate, key = :key, revoked = :revoked, expiry_time = :expiry_time WHERE serial_number = :serial_number`
res, err := repo.db.NamedExecContext(ctx, q, cert)
if err != nil {
return handleError(certs.ErrUpdateEntity, err)
}
count, err := res.RowsAffected()
if err != nil {
return errors.Wrap(certs.ErrUpdateEntity, err)
}
if count == 0 {
return certs.ErrNotFound
}
return nil
}

func (repo CSRRepo) RetrieveCSR(ctx context.Context,id string) (certs.CSR, error) {
q := `SELECT serial_number, certificate, key, entity_id, revoked, expiry_time FROM certs WHERE serial_number = $1`
var csr certs.CSR
if err := repo.db.QueryRowxContext(ctx, q, id).StructScan(&csr); err != nil {
if err == sql.ErrNoRows {
return certs.CSR{}, errors.Wrap(certs.ErrNotFound, err)
}
return certs.CSR{}, errors.Wrap(certs.ErrViewEntity, err)
}
return csr, nil
}

func (repo CSRRepo) ListCSRs(ctx context.Context, pm certs.PageMetadata) (certs.CSRPage, error) {
q := `SELECT serial_number, status, submitted_at, processed_at, entity_id FROM certs %s LIMIT :limit OFFSET :offset`
var condition string
if pm.EntityID != "" {
condition = `WHERE entity_id = :entity_id`
} else {
condition = ``
}
q = fmt.Sprintf(q, condition)
var csrs []certs.CSR

params := map[string]interface{}{
"limit": pm.Limit,
"offset": pm.Offset,
"entity_id": pm.EntityID,
}
rows, err := repo.db.NamedQueryContext(ctx, q, params)
if err != nil {
return certs.CSRPage{}, handleError(certs.ErrViewEntity, err)
}
defer rows.Close()

for rows.Next() {
csr := &certs.CSR{}
if err := rows.StructScan(csr); err != nil {
return certs.CSRPage{}, errors.Wrap(certs.ErrViewEntity, err)
}

csrs = append(csrs, *csr)
}

q = fmt.Sprintf(`SELECT COUNT(*) FROM certs %s LIMIT :limit OFFSET :offset`, condition)
pm.Total, err = repo.total(ctx, q, params)
if err != nil {
return certs.CSRPage{}, errors.Wrap(certs.ErrViewEntity, err)
}
return certs.CSRPage{
PageMetadata: pm,
CSRs: csrs,
}, nil
}

func (repo CSRRepo) total(ctx context.Context, query string, params interface{}) (uint64, error) {
rows, err := repo.db.NamedQueryContext(ctx, query, params)
if err != nil {
return 0, err
}
defer rows.Close()
total := uint64(0)
if rows.Next() {
if err := rows.Scan(&total); err != nil {
return 0, err
}
}
return total, nil
}

func handleError(wrapper, err error) error {
pqErr, ok := err.(*pgconn.PgError)
if ok {
switch pqErr.Code {
case errDuplicate:
return errors.Wrap(ErrConflict, err)
case errInvalid, errInvalidChar, errTruncation, errUntranslatable:
return errors.Wrap(ErrMalformedEntity, err)
case errFK:
return errors.Wrap(ErrCreateEntity, err)
}
}

return errors.Wrap(wrapper, err)
}
22 changes: 11 additions & 11 deletions postgres/csr/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ func Migration() *migrate.MemoryMigrationSource {
return &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "certs_1",
Id: "csr_1",
Up: []string{
`CREATE TABLE IF NOT EXISTS certs (
serial_number VARCHAR(40) UNIQUE NOT NULL,
certificate TEXT,
key TEXT,
revoked BOOLEAN,
expiry_time TIMESTAMP,
entity_id VARCHAR(36),
type TEXT CHECK (type IN ('RootCA', 'IntermediateCA', 'ClientCert')),
PRIMARY KEY (serial_number)
`CREATE TABLE IF NOT EXISTS csr (
serial_number VARCHAR(40),
csr TEXT,
private_key TEXT,
entity_id VARCHAR(36),
status BOOLEAN,
submitted_at TIMESTAMP,
processed_at TIMESTAMP,
PRIMARY KEY (entity_id)
)`,
},
Down: []string{
"DROP TABLE certs",
"DROP TABLE csr",
},
},
},
Expand Down
Loading

0 comments on commit cbaee01

Please sign in to comment.