SMQ-3028 - Unify SMQ certs with Absmach Certs (#3146)

Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
Steve Munene
2025-10-01 12:08:33 +03:00
committed by GitHub
parent b031dc08b8
commit ae7c93020a
69 changed files with 850 additions and 7510 deletions
-113
View File
@@ -1,113 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"github.com/spf13/cobra"
)
var cmdCerts = []cobra.Command{
{
Use: "get [<cert_serial> | client <client_id> ] <domain_id> <user_auth_token>",
Short: "Get certificate",
Long: `Gets a certificate for a given cert ID.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 3 {
logUsageCmd(*cmd, cmd.Use)
return
}
if args[0] == "client" {
cert, err := sdk.ViewCertByClient(cmd.Context(), args[1], args[2], args[3])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, cert)
return
}
cert, err := sdk.ViewCert(cmd.Context(), args[0], args[1], args[2])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, cert)
},
},
{
Use: "revoke-all <client_id> <domain_id> <user_auth_token>",
Short: "Revoke certificate",
Long: `Revokes a certificate for a given client ID.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
return
}
rtime, err := sdk.RevokeAllCerts(cmd.Context(), args[0], args[1], args[2])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logRevokedTimeCmd(*cmd, rtime)
},
},
{
Use: "revoke <cert_serial> <domain_id> <user_auth_token>",
Short: "Revoke certificate",
Long: `Revokes a certificate for a given cert serial.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
return
}
rtime, err := sdk.RevokeCert(cmd.Context(), args[0], args[1], args[2])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logRevokedTimeCmd(*cmd, rtime)
},
},
}
// NewCertsCmd returns certificate command.
func NewCertsCmd() *cobra.Command {
var ttl string
issueCmd := cobra.Command{
Use: "issue <client_id> <domain_id> <user_auth_token> [--ttl=8760h]",
Short: "Issue certificate",
Long: `Issues new certificate for a client`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 3 {
logUsageCmd(*cmd, cmd.Use)
return
}
clientID := args[0]
c, err := sdk.IssueCert(cmd.Context(), clientID, ttl, args[1], args[2])
if err != nil {
logErrorCmd(*cmd, err)
return
}
logJSONCmd(*cmd, c)
},
}
issueCmd.Flags().StringVar(&ttl, "ttl", "8760h", "certificate time to live in duration")
cmd := cobra.Command{
Use: "certs [issue | get | revoke ]",
Short: "Certificates management",
Long: `Certificates management: issue, get or revoke certificates for clients"`,
}
cmdCerts = append(cmdCerts, issueCmd)
for i := range cmdCerts {
cmd.AddCommand(&cmdCerts[i])
}
return &cmd
}
-341
View File
@@ -1,341 +0,0 @@
// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package cli_test
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
"time"
"github.com/absmach/supermq/cli"
"github.com/absmach/supermq/pkg/errors"
svcerr "github.com/absmach/supermq/pkg/errors/service"
mgsdk "github.com/absmach/supermq/pkg/sdk"
sdkmocks "github.com/absmach/supermq/pkg/sdk/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var cert = mgsdk.Cert{
ClientID: client.ID,
}
func TestGetCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
certCmd := cli.NewCertsCmd()
rootCmd := setFlags(certCmd)
var ct mgsdk.Cert
var cts mgsdk.CertSerials
cases := []struct {
desc string
args []string
sdkErr errors.SDKError
errLogMessage string
logType outputLog
serials mgsdk.CertSerials
cert mgsdk.Cert
}{
{
desc: "get cert successfully",
args: []string{
"client",
client.ID,
domainID,
validToken,
},
logType: entityLog,
serials: mgsdk.CertSerials{
PageRes: mgsdk.PageRes{
Total: 1,
Offset: 0,
Limit: 10,
},
Certs: []mgsdk.Cert{cert},
},
},
{
desc: "get cert successfully by id",
args: []string{
client.ID,
domainID,
validToken,
},
logType: entityLog,
cert: cert,
},
{
desc: "get cert with invalid token",
args: []string{
"client",
client.ID,
domainID,
invalidToken,
},
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
logType: errLog,
},
{
desc: "get cert by id with invalid token",
args: []string{
client.ID,
domainID,
invalidToken,
},
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
logType: errLog,
},
{
desc: "get cert with invalid args",
args: []string{
client.ID,
},
logType: usageLog,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("ViewCertByClient", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.serials, tc.sdkErr)
sdkCall1 := sdkMock.On("ViewCert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.cert, tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...)
switch tc.logType {
case entityLog:
if tc.args[1] == "client" {
err := json.Unmarshal([]byte(out), &cts)
assert.Nil(t, err)
assert.Equal(t, tc.serials, cts, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.serials, cts))
} else {
err := json.Unmarshal([]byte(out), &ct)
assert.Nil(t, err)
assert.Equal(t, tc.cert, ct, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, ct))
}
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()
sdkCall1.Unset()
})
}
}
func TestRevokeAllCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
certCmd := cli.NewCertsCmd()
rootCmd := setFlags(certCmd)
revokeTime := time.Now()
cases := []struct {
desc string
args []string
sdkErr errors.SDKError
logType outputLog
errLogMessage string
time time.Time
response string
}{
{
desc: "revoke cert successfully",
args: []string{
client.ID,
domainID,
token,
},
logType: revokeLog,
response: fmt.Sprintf("\nrevoked: %s\n\n", revokeTime),
time: revokeTime,
},
{
desc: "revoke cert with invalid args",
args: []string{
client.ID,
domainID,
token,
extraArg,
},
logType: usageLog,
},
{
desc: "revoke cert with invalid token",
args: []string{
client.ID,
domainID,
invalidToken,
},
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
logType: errLog,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("RevokeAllCerts", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.time, tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{revokeAllCmd}, tc.args...)...)
switch tc.logType {
case revokeLog:
assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, 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 TestRevokeCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
certCmd := cli.NewCertsCmd()
rootCmd := setFlags(certCmd)
revokeTime := time.Now()
cases := []struct {
desc string
args []string
sdkErr errors.SDKError
logType outputLog
errLogMessage string
time time.Time
response string
}{
{
desc: "revoke cert successfully",
args: []string{
cert.SerialNumber,
domainID,
token,
},
logType: revokeLog,
response: fmt.Sprintf("\nrevoked: %s\n\n", revokeTime),
time: revokeTime,
},
{
desc: "revoke cert with invalid args",
args: []string{
cert.SerialNumber,
domainID,
token,
extraArg,
},
logType: usageLog,
},
{
desc: "revoke cert with invalid token",
args: []string{
cert.SerialNumber,
domainID,
invalidToken,
},
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
logType: errLog,
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("RevokeCert", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.time, tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{revokeCmd}, tc.args...)...)
switch tc.logType {
case revokeLog:
assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, 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 TestIssueCertCmd(t *testing.T) {
sdkMock := new(sdkmocks.SDK)
cli.SetSDK(sdkMock)
certCmd := cli.NewCertsCmd()
rootCmd := setFlags(certCmd)
cert := mgsdk.Cert{
SerialNumber: "serial",
}
var cs mgsdk.Cert
cases := []struct {
desc string
args []string
logType outputLog
errLogMessage string
sdkErr errors.SDKError
cert mgsdk.Cert
}{
{
desc: "issue cert successfully",
args: []string{
client.ID,
domainID,
validToken,
},
cert: cert,
logType: entityLog,
},
{
desc: "issue cert with invalid args",
args: []string{
client.ID,
domainID,
validToken,
extraArg,
},
logType: usageLog,
},
{
desc: "issue cert with invalid token",
args: []string{
client.ID,
domainID,
invalidToken,
},
logType: errLog,
sdkErr: errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
sdkCall := sdkMock.On("IssueCert", mock.Anything, mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.cert, tc.sdkErr)
out := executeCommand(t, rootCmd, append([]string{issueCmd}, tc.args...)...)
switch tc.logType {
case usageLog:
assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", 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 entityLog:
err := json.Unmarshal([]byte(out), &cs)
assert.Nil(t, err)
assert.Equal(t, tc.cert, cs, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.cert, cs))
}
sdkCall.Unset()
})
}
}
-7
View File
@@ -31,13 +31,6 @@ const (
usersCmd = "users"
)
// Certs commands
const (
revokeCmd = "revoke"
revokeAllCmd = "revoke-all"
issueCmd = "issue"
)
// Messages commands
const sendCmd = "send"
-2
View File
@@ -24,7 +24,6 @@ const (
defDomainsURL string = defURL + ":9003"
defChannelsURL string = defURL + ":9005"
defGroupsURL string = defURL + ":9004"
defCertsURL string = defURL + ":9019"
defHTTPURL string = defURL + ":8008"
defJournalURL string = defURL + ":9021"
defTLSVerification bool = false
@@ -111,7 +110,6 @@ func ParseConfig(sdkConf smqsdk.Config) (smqsdk.Config, error) {
ChannelsURL: defChannelsURL,
GroupsURL: defGroupsURL,
HTTPAdapterURL: defHTTPURL,
CertsURL: defCertsURL,
JournalURL: defJournalURL,
HostURL: defURL,
TLSVerification: defTLSVerification,
-9
View File
@@ -6,7 +6,6 @@ package cli
import (
"encoding/json"
"fmt"
"time"
"github.com/fatih/color"
"github.com/hokaccha/go-prettyjson"
@@ -77,14 +76,6 @@ func logOKCmd(cmd cobra.Command) {
fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n\n", color.BlueString("ok"))
}
func logRevokedTimeCmd(cmd cobra.Command, t time.Time) {
if RawOutput {
fmt.Fprintln(cmd.OutOrStdout(), t)
} else {
fmt.Fprintf(cmd.OutOrStdout(), color.BlueString("\nrevoked: %v\n\n"), t)
}
}
func convertMetadata(m string) (map[string]any, error) {
var metadata map[string]any
if m == "" {