Skip to content

Commit

Permalink
NOISSUE - Add delete certs feature (#46)
Browse files Browse the repository at this point in the history
* Add delete feature

Signed-off-by: nyagamunene <[email protected]>

* Remove plural words in methods

Signed-off-by: nyagamunene <[email protected]>

---------

Signed-off-by: nyagamunene <[email protected]>
  • Loading branch information
nyagamunene authored Nov 18, 2024
1 parent 6f4a67a commit bdaaa5f
Show file tree
Hide file tree
Showing 18 changed files with 402 additions and 6 deletions.
19 changes: 17 additions & 2 deletions api/http/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,32 @@ func revokeCertEndpoint(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(viewReq)
if err := req.validate(); err != nil {
return revokeCertRes{}, err
return revokeCertRes{revoked: false}, err
}

if err = svc.RevokeCert(ctx, req.id); err != nil {
return revokeCertRes{}, err
return revokeCertRes{revoked: false}, err
}

return revokeCertRes{revoked: true}, nil
}
}

func deleteCertEndpoint(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(deleteReq)
if err := req.validate(); err != nil {
return deleteCertRes{deleted: false}, err
}

if err = svc.RemoveCert(ctx, req.entityID); err != nil {
return deleteCertRes{deleted: false}, err
}

return deleteCertRes{deleted: true}, nil
}
}

func requestCertDownloadTokenEndpoint(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(viewReq)
Expand Down
3 changes: 3 additions & 0 deletions api/http/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ var (

// ErrMissingCN indicates missing common name.
ErrMissingCN = errors.New("missing common name")

// ErrEmptyEntityID indicates that the entity id is empty.
ErrEmptyEntityID = errors.New("missing entity id")
)
11 changes: 11 additions & 0 deletions api/http/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ func (req viewReq) validate() error {
return nil
}

type deleteReq struct {
entityID string
}

func (req deleteReq) validate() error {
if req.entityID == "" {
return errors.Wrap(certs.ErrMalformedEntity, ErrEmptyEntityID)
}
return nil
}

type crlReq struct {
certtype certs.CertType
}
Expand Down
24 changes: 22 additions & 2 deletions api/http/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ type revokeCertRes struct {

func (res revokeCertRes) Code() int {
if res.revoked {
return http.StatusOK
return http.StatusNoContent
}

return http.StatusBadRequest
return http.StatusUnprocessableEntity
}

func (res revokeCertRes) Headers() map[string]string {
Expand All @@ -59,6 +59,26 @@ func (res revokeCertRes) Empty() bool {
return true
}

type deleteCertRes struct {
deleted bool
}

func (res deleteCertRes) Code() int {
if res.deleted {
return http.StatusNoContent
}

return http.StatusUnprocessableEntity
}

func (res deleteCertRes) Headers() map[string]string {
return map[string]string{}
}

func (res deleteCertRes) Empty() bool {
return true
}

type requestCertDownloadTokenRes struct {
Token string `json:"token"`
}
Expand Down
13 changes: 13 additions & 0 deletions api/http/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ func MakeHandler(svc certs.Service, logger *slog.Logger, instanceID string) http
EncodeResponse,
opts...,
), "revoke_cert").ServeHTTP)
r.Delete("/{entityID}/delete", otelhttp.NewHandler(kithttp.NewServer(
deleteCertEndpoint(svc),
decodeDelete,
EncodeResponse,
opts...,
), "delete_cert").ServeHTTP)
r.Get("/{id}/download/token", otelhttp.NewHandler(kithttp.NewServer(
requestCertDownloadTokenEndpoint(svc),
decodeView,
Expand Down Expand Up @@ -133,6 +139,13 @@ func decodeView(_ context.Context, r *http.Request) (interface{}, error) {
return req, nil
}

func decodeDelete(_ context.Context, r *http.Request) (interface{}, error) {
req := deleteReq{
entityID: chi.URLParam(r, "entityID"),
}
return req, nil
}

func decodeCRL(_ context.Context, r *http.Request) (interface{}, error) {
certType, err := readNumQuery(r, "", defType)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions api/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ func (lm *loggingMiddleware) ListCerts(ctx context.Context, pm certs.PageMetadat
return lm.svc.ListCerts(ctx, pm)
}

func (lm *loggingMiddleware) RemoveCert(ctx context.Context, entityId string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_cert took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(message)
}(time.Now())
return lm.svc.RemoveCert(ctx, entityId)
}

func (lm *loggingMiddleware) ViewCert(ctx context.Context, serialNumber string) (cert certs.Certificate, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view_cert for serial number %s took %s to complete", serialNumber, time.Since(begin))
Expand Down
8 changes: 8 additions & 0 deletions api/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ func (mm *metricsMiddleware) ListCerts(ctx context.Context, pm certs.PageMetadat
return mm.svc.ListCerts(ctx, pm)
}

func (mm *metricsMiddleware) RemoveCert(ctx context.Context, entityId string) error {
defer func(begin time.Time) {
mm.counter.With("method", "remove_certificate").Add(1)
mm.latency.With("method", "remove_certificate").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveCert(ctx, entityId)
}

func (mm *metricsMiddleware) ViewCert(ctx context.Context, serialNumber string) (certs.Certificate, error) {
defer func(begin time.Time) {
mm.counter.With("method", "view_certificate").Add(1)
Expand Down
6 changes: 6 additions & 0 deletions certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ type Service interface {

// GetChainCA retrieves the chain of CA i.e. root and intermediate cert concat together.
GetChainCA(ctx context.Context, token string) (Certificate, error)

// RemoveCert deletes a cert for a provided entityID.
RemoveCert(ctx context.Context, entityId string) error
}

type Repository interface {
Expand All @@ -90,4 +93,7 @@ type Repository interface {

// ListRevokedCerts retrieves revoked lists from database.
ListRevokedCerts(ctx context.Context) ([]Certificate, error)

// RemoveCert deletes cert from database.
RemoveCert(ctx context.Context, entityId string) error
}
17 changes: 17 additions & 0 deletions cli/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ var cmdCerts = []cobra.Command{
logOKCmd(*cmd)
},
},
{
Use: "delete <entity_id> ",
Short: "Delete certificate",
Long: `Deletes certificates for a given entity id.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
logUsageCmd(*cmd, cmd.Use)
return
}
err := sdk.DeleteCert(args[0])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logOKCmd(*cmd)
},
},
{
Use: "renew <serial_number> ",
Short: "Renew certificate",
Expand Down
57 changes: 57 additions & 0 deletions cli/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

const (
revokeCmd = "revoke"
deleteCmd = "delete"
issueCmd = "issue"
renewCmd = "renew"
listCmd = "get"
Expand Down Expand Up @@ -175,6 +176,62 @@ func TestRevokeCertCmd(t *testing.T) {
}
}

func TestDeleteCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.MockSDK)
cli.SetSDK(sdkMock)
certCmd := cli.NewCertsCmd()
rootCmd := setFlags(certCmd)

cases := []struct {
desc string
args []string
sdkErr errors.SDKError
errLogMessage string
logType outputLog
}{
{
desc: "delete certs successfully",
args: []string{
id,
},
logType: okLog,
},
{
desc: "delete certs with invalid args",
args: []string{
id,
extraArg,
},
logType: usageLog,
},
{
desc: "delete certs failed",
args: []string{
id,
},
sdkErr: errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(certs.ErrUpdateEntity, http.StatusUnprocessableEntity)),
logType: errLog,
},
}

for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("DeleteCerts", mock.Anything).Return(tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{deleteCmd}, tc.args...)...)
switch tc.logType {
case okLog:
assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
case errLog:
assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
case usageLog:
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
}
sdkCall.Unset()
})
}
}

func TestRenewCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.MockSDK)
cli.SetSDK(sdkMock)
Expand Down
47 changes: 47 additions & 0 deletions mocks/repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions mocks/service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bdaaa5f

Please sign in to comment.