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

NOISSUE - Add delete certs feature #46

Merged
merged 2 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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.RemoveCerts(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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use certID or just id.

}

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) RemoveCerts(ctx context.Context, entityId string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_certs 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.RemoveCerts(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) RemoveCerts(ctx context.Context, entityId string) error {
defer func(begin time.Time) {
mm.counter.With("method", "remove_certificates").Add(1)
mm.latency.With("method", "remove_certificates").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.RemoveCerts(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)

// RemoveCerts deletes a certs for a provided entityID.
RemoveCerts(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)

// RemoveCerts deletes certs from database.
RemoveCerts(ctx context.Context, entityId string) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use singular RemovCert.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also certID or id.

}
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.DeleteCerts(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
Loading