NOISSUE - Add cert revocation to SDK (#1693)

* initial commit

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* fix certificate revoking

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* change from mapstructure to json

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* add comments to serial modification

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* fix typo

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* update vault docker version

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* write env variables

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* change env path

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* return revocation time

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* revert to intermediate CA

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove deadcode

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* make revoke cert output readable

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove keybits and keytype

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove dead code

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* make inline

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* add empty line

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove commented code

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove keyBits

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

* remove keyBits

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>

Signed-off-by: rodneyosodo <socials@rodneyosodo.com>
Co-authored-by: rodneyosodo <socials@rodneyosodo.com>
This commit is contained in:
b1ackd0t
2023-01-13 16:33:00 +03:00
committed by GitHub
parent b83a344093
commit d008ae5d97
27 changed files with 195 additions and 229 deletions
-6
View File
@@ -198,18 +198,12 @@ components:
required:
- thing_id
- ttl
- key_bits
- key_type
properties:
thing_id:
type: string
format: uuid
ttl:
type: string
key_type:
type: string
key_bits:
type: integer
responses:
ServiceError:
+1 -1
View File
@@ -12,7 +12,7 @@ To issue a certificate:
TOK=`curl -s --insecure -S -X POST http://localhost/tokens -H 'Content-Type: application/json' -d '{"email":"edge@email.com","password":"12345678"}' | jq -r '.token'`
curl -s -S -X POST http://localhost:8204/certs -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"thing_id":<thing_id>, "key_bits":2048, "key_type":"rsa"}'
curl -s -S -X POST http://localhost:8204/certs -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' -d '{"thing_id":<thing_id>}'
```
```json
+8 -2
View File
@@ -16,7 +16,7 @@ func issueCert(svc certs.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return nil, err
}
res, err := svc.IssueCert(ctx, req.token, req.ThingID, req.TTL, req.KeyBits, req.KeyType)
res, err := svc.IssueCert(ctx, req.token, req.ThingID, req.TTL)
if err != nil {
return certsRes{}, err
}
@@ -91,6 +91,12 @@ func revokeCert(svc certs.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return nil, err
}
return svc.RevokeCert(ctx, req.token, req.certID)
res, err := svc.RevokeCert(ctx, req.token, req.certID)
if err != nil {
return nil, err
}
return revokeCertsRes{
RevocationTime: res.RevocationTime,
}, nil
}
}
+2 -2
View File
@@ -26,7 +26,7 @@ func NewLoggingMiddleware(svc certs.Service, logger log.Logger) certs.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, ttl string, keyBits int, keyType string) (c certs.Cert, err error) {
func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, ttl string) (c certs.Cert, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method issue_cert for token: %s and thing: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
@@ -36,7 +36,7 @@ func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, ttl
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.IssueCert(ctx, token, thingID, ttl, keyBits, keyType)
return lm.svc.IssueCert(ctx, token, thingID, ttl)
}
func (lm *loggingMiddleware) ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (cp certs.Page, err error) {
+2 -2
View File
@@ -30,13 +30,13 @@ func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metri
}
}
func (ms *metricsMiddleware) IssueCert(ctx context.Context, token, thingID string, ttl string, keyBits int, keyType string) (certs.Cert, error) {
func (ms *metricsMiddleware) IssueCert(ctx context.Context, token, thingID, ttl string) (certs.Cert, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue_cert").Add(1)
ms.latency.With("method", "issue_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.IssueCert(ctx, token, thingID, ttl, keyBits, keyType)
return ms.svc.IssueCert(ctx, token, thingID, ttl)
}
func (ms *metricsMiddleware) ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (certs.Page, error) {
+1 -3
View File
@@ -10,8 +10,6 @@ const maxLimitSize = 100
type addCertsReq struct {
token string
ThingID string `json:"thing_id"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
TTL string `json:"ttl"`
}
@@ -24,7 +22,7 @@ func (req addCertsReq) validate() error {
return apiutil.ErrMissingID
}
if req.TTL == "" || req.KeyType == "" || req.KeyBits == 0 {
if req.TTL == "" {
return apiutil.ErrMissingCertData
}
+16
View File
@@ -28,6 +28,10 @@ type certsRes struct {
created bool
}
type revokeCertsRes struct {
RevocationTime time.Time `json:"revocation_time"`
}
func (res certsPageRes) Code() int {
return http.StatusOK
}
@@ -55,3 +59,15 @@ func (res certsRes) Headers() map[string]string {
func (res certsRes) Empty() bool {
return false
}
func (res revokeCertsRes) Code() int {
return http.StatusOK
}
func (res revokeCertsRes) Headers() map[string]string {
return map[string]string{}
}
func (res revokeCertsRes) Empty() bool {
return false
}
+5 -5
View File
@@ -21,6 +21,8 @@ import (
"github.com/mainflux/mainflux/pkg/errors"
)
const keyBits = 2048
var (
errPrivateKeyEmpty = errors.New("private key is empty")
errPrivateKeyUnsupportedType = errors.New("private key type is unsupported")
@@ -32,25 +34,23 @@ type agent struct {
AuthTimeout time.Duration
TLSCert tls.Certificate
X509Cert *x509.Certificate
RSABits int
TTL string
mu sync.Mutex
counter uint64
certs map[string]pki.Cert
}
func NewPkiAgent(tlsCert tls.Certificate, caCert *x509.Certificate, keyBits int, ttl string, timeout time.Duration) pki.Agent {
func NewPkiAgent(tlsCert tls.Certificate, caCert *x509.Certificate, ttl string, timeout time.Duration) pki.Agent {
return &agent{
AuthTimeout: timeout,
TLSCert: tlsCert,
X509Cert: caCert,
RSABits: keyBits,
TTL: ttl,
certs: make(map[string]pki.Cert),
}
}
func (a *agent) IssueCert(cn string, ttl, keyType string, keyBits int) (pki.Cert, error) {
func (a *agent) IssueCert(cn, ttl string) (pki.Cert, error) {
a.mu.Lock()
defer a.mu.Unlock()
@@ -139,7 +139,7 @@ func (a *agent) IssueCert(cn string, ttl, keyType string, keyBits int) (pki.Cert
ClientCert: cert,
ClientKey: key,
Serial: x509cert.SerialNumber.String(),
Expire: x509cert.NotAfter,
Expire: x509cert.NotAfter.Unix(),
IssuingCA: x509cert.Issuer.String(),
}, nil
}
+29 -80
View File
@@ -6,8 +6,7 @@ package pki
import (
"encoding/json"
"io/ioutil"
"net/http"
"fmt"
"time"
"github.com/hashicorp/vault/api"
@@ -19,7 +18,6 @@ const (
issue = "issue"
cert = "cert"
revoke = "revoke"
apiVer = "v1"
)
var (
@@ -32,25 +30,23 @@ var (
// ErrFailedCertRevocation indicates failed certificate revocation
ErrFailedCertRevocation = errors.New("failed to revoke certificate")
errFailedVaultCertIssue = errors.New("failed to issue vault certificate")
errFailedVaultRead = errors.New("failed to read vault certificate")
errFailedCertDecoding = errors.New("failed to decode response from vault service")
errFailedCertDecoding = errors.New("failed to decode response from vault service")
)
type Cert struct {
ClientCert string `json:"client_cert" mapstructure:"certificate"`
IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"`
CAChain []string `json:"ca_chain" mapstructure:"ca_chain"`
ClientKey string `json:"client_key" mapstructure:"private_key"`
PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"`
Serial string `json:"serial" mapstructure:"serial_number"`
Expire time.Time `json:"expire" mapstructure:"-"`
ClientCert string `json:"client_cert" mapstructure:"certificate"`
IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"`
CAChain []string `json:"ca_chain" mapstructure:"ca_chain"`
ClientKey string `json:"client_key" mapstructure:"private_key"`
PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"`
Serial string `json:"serial" mapstructure:"serial_number"`
Expire int64 `json:"expire" mapstructure:"expiration"`
}
// Agent represents the Vault PKI interface.
type Agent interface {
// IssueCert issues certificate on PKI
IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error)
IssueCert(cn, ttl string) (Cert, error)
// Read retrieves certificate from PKI
Read(serial string) (Cert, error)
@@ -73,8 +69,6 @@ type pkiAgent struct {
type certReq struct {
CommonName string `json:"common_name"`
TTL string `json:"ttl"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
}
type certRevokeReq struct {
@@ -83,9 +77,8 @@ type certRevokeReq struct {
// NewVaultClient instantiates a Vault client.
func NewVaultClient(token, host, path, role string) (Agent, error) {
conf := &api.Config{
Address: host,
}
conf := api.DefaultConfig()
conf.Address = host
client, err := api.NewClient(conf)
if err != nil {
@@ -98,44 +91,29 @@ func NewVaultClient(token, host, path, role string) (Agent, error) {
role: role,
path: path,
client: client,
issueURL: "/" + apiVer + "/" + path + "/" + issue + "/" + role,
readURL: "/" + apiVer + "/" + path + "/" + cert + "/",
revokeURL: "/" + apiVer + "/" + path + "/" + revoke,
issueURL: "/" + path + "/" + issue + "/" + role,
readURL: "/" + path + "/" + cert + "/",
revokeURL: "/" + path + "/" + revoke,
}
return &p, nil
}
func (p *pkiAgent) IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error) {
func (p *pkiAgent) IssueCert(cn, ttl string) (Cert, error) {
cReq := certReq{
CommonName: cn,
TTL: ttl,
KeyBits: keyBits,
KeyType: keyType,
}
r := p.client.NewRequest("POST", p.issueURL)
if err := r.SetJSONBody(cReq); err != nil {
return Cert{}, err
}
resp, err := p.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
TTL: fmt.Sprintf("%sh", ttl),
}
var certIssueReq map[string]interface{}
data, err := json.Marshal(cReq)
if err != nil {
return Cert{}, err
}
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Cert{}, err
}
return Cert{}, errors.Wrap(errFailedVaultCertIssue, err)
if err := json.Unmarshal(data, &certIssueReq); err != nil {
return Cert{}, nil
}
s, err := api.ParseSecret(resp.Body)
s, err := p.client.Logical().Write(p.issueURL, certIssueReq)
if err != nil {
return Cert{}, err
}
@@ -149,32 +127,14 @@ func (p *pkiAgent) IssueCert(cn string, ttl, keyType string, keyBits int) (Cert,
}
func (p *pkiAgent) Read(serial string) (Cert, error) {
r := p.client.NewRequest("GET", p.readURL+"/"+serial)
resp, err := p.client.RawRequest(r)
s, err := p.client.Logical().Read(p.readURL + serial)
if err != nil {
return Cert{}, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Cert{}, err
}
return Cert{}, errors.Wrap(errFailedVaultRead, err)
}
s, err := api.ParseSecret(resp.Body)
if err != nil {
return Cert{}, err
}
cert := Cert{}
if err = mapstructure.Decode(s.Data, &cert); err != nil {
return Cert{}, errors.Wrap(errFailedCertDecoding, err)
}
return cert, nil
}
@@ -183,34 +143,23 @@ func (p *pkiAgent) Revoke(serial string) (time.Time, error) {
SerialNumber: serial,
}
r := p.client.NewRequest("POST", p.revokeURL)
if err := r.SetJSONBody(cReq); err != nil {
return time.Time{}, err
}
resp, err := p.client.RawRequest(r)
var certRevokeReq map[string]interface{}
data, err := json.Marshal(cReq)
if err != nil {
return time.Time{}, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return time.Time{}, err
}
return time.Time{}, errors.Wrap(errFailedVaultCertIssue, err)
if err := json.Unmarshal(data, &certRevokeReq); err != nil {
return time.Time{}, nil
}
s, err := api.ParseSecret(resp.Body)
s, err := p.client.Logical().Write(p.revokeURL, certRevokeReq)
if err != nil {
return time.Time{}, err
}
rev, err := s.Data["revocation_time"].(json.Number).Float64()
if err != nil {
return time.Time{}, err
}
return time.Unix(0, int64(rev)*int64(time.Millisecond)), nil
return time.Unix(0, int64(rev)*int64(time.Second)), nil
}
+4 -4
View File
@@ -31,7 +31,7 @@ var _ Service = (*certsService)(nil)
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// IssueCert issues certificate for given thing id if access is granted with token
IssueCert(ctx context.Context, token, thingID, ttl string, keyBits int, keyType string) (Cert, error)
IssueCert(ctx context.Context, token, thingID, ttl string) (Cert, error)
// ListCerts lists certificates issued for a given thing ID
ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (Page, error)
@@ -105,7 +105,7 @@ type Cert struct {
Expire time.Time `json:"expire" mapstructure:"-"`
}
func (cs *certsService) IssueCert(ctx context.Context, token, thingID string, ttl string, keyBits int, keyType string) (Cert, error) {
func (cs *certsService) IssueCert(ctx context.Context, token, thingID string, ttl string) (Cert, error) {
owner, err := cs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return Cert{}, err
@@ -116,7 +116,7 @@ func (cs *certsService) IssueCert(ctx context.Context, token, thingID string, tt
return Cert{}, errors.Wrap(ErrFailedCertCreation, err)
}
cert, err := cs.pki.IssueCert(thing.Key, ttl, keyType, keyBits)
cert, err := cs.pki.IssueCert(thing.Key, ttl)
if err != nil {
return Cert{}, errors.Wrap(ErrFailedCertCreation, err)
}
@@ -130,7 +130,7 @@ func (cs *certsService) IssueCert(ctx context.Context, token, thingID string, tt
ClientKey: cert.ClientKey,
PrivateKeyType: cert.PrivateKeyType,
Serial: cert.Serial,
Expire: cert.Expire,
Expire: time.Unix(0, int64(cert.Expire)*int64(time.Second)),
}
_, err = cs.certsRepo.Save(context.Background(), c)
+6 -33
View File
@@ -40,8 +40,6 @@ const (
thingKey = "thingKey"
thingID = "1"
ttl = "1h"
keyBits = 2048
key = "rsa"
certNum = 10
cfgLogLevel = "error"
@@ -96,7 +94,7 @@ func newService(tokens map[string]string) (certs.Service, error) {
SignRSABits: cfgSignRSABits,
}
pki := mocks.NewPkiAgent(tlsCert, caCert, cfgSignRSABits, cfgSignHoursValid, authTimeout)
pki := mocks.NewPkiAgent(tlsCert, caCert, cfgSignHoursValid, authTimeout)
return certs.New(auth, repo, sdk, c, pki), nil
}
@@ -125,7 +123,6 @@ func TestIssueCert(t *testing.T) {
thingID string
ttl string
key string
keyBits int
err error
}{
{
@@ -133,8 +130,6 @@ func TestIssueCert(t *testing.T) {
token: token,
thingID: thingID,
ttl: ttl,
key: key,
keyBits: 2048,
err: nil,
},
{
@@ -142,8 +137,6 @@ func TestIssueCert(t *testing.T) {
token: token,
thingID: "2",
ttl: ttl,
key: key,
keyBits: 2048,
err: certs.ErrFailedCertCreation,
},
{
@@ -151,32 +144,12 @@ func TestIssueCert(t *testing.T) {
token: wrongValue,
thingID: thingID,
ttl: ttl,
key: key,
keyBits: 2048,
err: errors.ErrAuthentication,
},
{
desc: "issue new cert for bad key bits",
token: token,
thingID: thingID,
ttl: ttl,
key: key,
keyBits: -2,
err: certs.ErrFailedCertCreation,
},
{
desc: "issue new cert for bad key bits",
token: token,
thingID: thingID,
ttl: ttl,
key: key,
keyBits: -2,
err: certs.ErrFailedCertCreation,
},
}
for _, tc := range cases {
c, err := svc.IssueCert(context.Background(), tc.token, tc.thingID, tc.ttl, tc.keyBits, tc.key)
c, err := svc.IssueCert(context.Background(), tc.token, tc.thingID, tc.ttl)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
cert, _ := readCert([]byte(c.ClientCert))
if cert != nil {
@@ -190,7 +163,7 @@ func TestRevokeCert(t *testing.T) {
svc, err := newService(map[string]string{token: email})
require.Nil(t, err, fmt.Sprintf("unexpected service creation error: %s\n", err))
_, err = svc.IssueCert(context.Background(), token, thingID, ttl, keyBits, key)
_, err = svc.IssueCert(context.Background(), token, thingID, ttl)
require.Nil(t, err, fmt.Sprintf("unexpected service creation error: %s\n", err))
cases := []struct {
@@ -231,7 +204,7 @@ func TestListCerts(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("unexpected service creation error: %s\n", err))
for i := 0; i < certNum; i++ {
_, err = svc.IssueCert(context.Background(), token, thingID, ttl, keyBits, key)
_, err = svc.IssueCert(context.Background(), token, thingID, ttl)
require.Nil(t, err, fmt.Sprintf("unexpected cert creation error: %s\n", err))
}
@@ -296,7 +269,7 @@ func TestListSerials(t *testing.T) {
var issuedCerts []certs.Cert
for i := 0; i < certNum; i++ {
cert, err := svc.IssueCert(context.Background(), token, thingID, ttl, keyBits, key)
cert, err := svc.IssueCert(context.Background(), token, thingID, ttl)
require.Nil(t, err, fmt.Sprintf("unexpected cert creation error: %s\n", err))
crt := certs.Cert{
@@ -366,7 +339,7 @@ func TestViewCert(t *testing.T) {
svc, err := newService(map[string]string{token: email})
require.Nil(t, err, fmt.Sprintf("unexpected service creation error: %s\n", err))
ic, err := svc.IssueCert(context.Background(), token, thingID, ttl, keyBits, key)
ic, err := svc.IssueCert(context.Background(), token, thingID, ttl)
require.Nil(t, err, fmt.Sprintf("unexpected cert creation error: %s\n", err))
cert := certs.Cert{
+42 -11
View File
@@ -6,14 +6,49 @@ import (
"github.com/spf13/cobra"
)
var cmdCerts = []cobra.Command{
{
Use: "get <cert_serial> <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) != 2 {
logUsage(cmd.Use)
return
}
cert, err := sdk.ViewCert(args[0], args[1])
if err != nil {
logError(err)
return
}
logJSON(cert)
},
},
{
Use: "revoke <thing_id> <user_auth_token>",
Short: "Revoke certificate",
Long: `Revokes a certificate for a given thing ID.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 {
logUsage(cmd.Use)
return
}
rtime, err := sdk.RevokeCert(args[0], args[1])
if err != nil {
logError(err)
return
}
logRevokedTime(rtime)
},
},
}
// NewCertsCmd returns certificate command.
func NewCertsCmd() *cobra.Command {
var keySize uint16
var keyType string
var ttl uint32
issueCmd := cobra.Command{
Use: "issue <thing_id> <user_auth_token> [--keysize=2048] [--keytype=rsa] [--ttl=8760]",
Use: "issue <thing_id> <user_auth_token> [--ttl=8760]",
Short: "Issue certificate",
Long: `Issues new certificate for a thing`,
Run: func(cmd *cobra.Command, args []string) {
@@ -25,7 +60,7 @@ func NewCertsCmd() *cobra.Command {
thingID := args[0]
valid := strconv.FormatUint(uint64(ttl), 10)
c, err := sdk.IssueCert(thingID, int(keySize), keyType, valid, args[1])
c, err := sdk.IssueCert(thingID, valid, args[1])
if err != nil {
logError(err)
return
@@ -34,19 +69,15 @@ func NewCertsCmd() *cobra.Command {
},
}
issueCmd.Flags().Uint16Var(&keySize, "keysize", 2048, "certificate key strength in bits: 2048, 4096 (RSA) or 224, 256, 384, 512 (EC)")
issueCmd.Flags().StringVar(&keyType, "keytype", "rsa", "certificate key type: RSA or EC")
issueCmd.Flags().Uint32Var(&ttl, "ttl", 8760, "certificate time to live in hours")
cmd := cobra.Command{
Use: "certs [issue | get]",
Use: "certs [issue | get | revoke ]",
Short: "Certificates management",
Long: `Certificates management: create certificates for things"`,
Long: `Certificates management: issue, get or revoke certificates for things"`,
}
cmdCerts := []cobra.Command{
issueCmd,
}
cmdCerts = append(cmdCerts, issueCmd)
for i := range cmdCerts {
cmd.AddCommand(&cmdCerts[i])
+9
View File
@@ -6,6 +6,7 @@ package cli
import (
"encoding/json"
"fmt"
"time"
"github.com/fatih/color"
prettyjson "github.com/hokaccha/go-prettyjson"
@@ -71,6 +72,14 @@ func logCreated(e string) {
}
}
func logRevokedTime(t time.Time) {
if RawOutput {
fmt.Println(t)
} else {
fmt.Printf(color.BlueString("\nrevoked: %v\n\n"), t)
}
}
func convertMetadata(m string) (map[string]interface{}, error) {
var metadata map[string]interface{}
if m == "" {
+1 -9
View File
@@ -43,7 +43,6 @@ const (
defBSAutoWhitelist = "true"
defBSContent = ""
defCertsHoursValid = "2400h"
defCertsKeyBits = "4096"
envConfigFile = "MF_PROVISION_CONFIG_FILE"
envLogLevel = "MF_PROVISION_LOG_LEVEL"
@@ -64,7 +63,6 @@ const (
envBSAutoWhiteList = "MF_PROVISION_BS_AUTO_WHITELIST"
envBSContent = "MF_PROVISION_BS_CONTENT"
envCertsHoursValid = "MF_PROVISION_CERTS_HOURS_VALID"
envCertsKeyBits = "MF_PROVISION_CERTS_RSA_BITS"
contentType = "application/json"
)
@@ -76,7 +74,6 @@ var (
errFailGettingCertSettings = errors.New("failed to get certificate file setting")
errFailGettingTLSConf = errors.New("failed to get TLS setting")
errFailGettingProvBS = errors.New("failed to get BS url setting")
errFailSettingKeyBits = errors.New("failed to set rsa number of bits")
errFailedToReadBootstrapContent = errors.New("failed to read bootstrap content from envs")
)
@@ -198,10 +195,6 @@ func loadConfig() (provision.Config, error) {
if autoWhiteList && !provisionBS {
return provision.Config{}, errors.New("Can't auto whitelist if auto config save is off")
}
keyBits, err := strconv.Atoi(mainflux.Env(envCertsKeyBits, defCertsKeyBits))
if err != nil && provisionX509 {
return provision.Config{}, errFailSettingKeyBits
}
var content map[string]interface{}
if c := mainflux.Env(envBSContent, defBSContent); c != "" {
@@ -227,8 +220,7 @@ func loadConfig() (provision.Config, error) {
TLS: tls,
},
Cert: provision.Cert{
TTL: mainflux.Env(envCertsHoursValid, defCertsHoursValid),
KeyBits: keyBits,
TTL: mainflux.Env(envCertsHoursValid, defCertsHoursValid),
},
Bootstrap: provision.Bootstrap{
X509Provision: provisionX509,
-1
View File
@@ -155,7 +155,6 @@ MF_PROVISION_BS_SVC_WHITELIST_URL=http://bootstrap:8202/things/state
MF_PROVISION_BS_CONFIG_PROVISIONING=true
MF_PROVISION_BS_AUTO_WHITELIST=true
MF_PROVISION_BS_CONTENT=
MF_PROVISION_CERTS_RSA_BITS=4096
MF_PROVISION_CERTS_HOURS_VALID=2400h
# Certs
+1 -1
View File
@@ -19,7 +19,7 @@ volumes:
services:
vault:
image: vault:1.6.2
image: vault:1.12.2
container_name: mainflux-vault
ports:
- ${MF_VAULT_PORT}:8200
+11
View File
@@ -1,6 +1,15 @@
#!/usr/bin/bash
set -euo pipefail
scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
export MAINFLUX_DIR=$scriptdir/../../../
write_env() {
sed -i "s,MF_VAULT_UNSEAL_KEY_1=.*,MF_VAULT_UNSEAL_KEY_1=$(awk -F ": " '$1 == "Unseal Key 1" {print $2}' data/secrets)," $MAINFLUX_DIR/docker/.env
sed -i "s,MF_VAULT_UNSEAL_KEY_2=.*,MF_VAULT_UNSEAL_KEY_2=$(awk -F ": " '$1 == "Unseal Key 2" {print $2}' data/secrets)," $MAINFLUX_DIR/docker/.env
sed -i "s,MF_VAULT_UNSEAL_KEY_3=.*,MF_VAULT_UNSEAL_KEY_3=$(awk -F ": " '$1 == "Unseal Key 3" {print $2}' data/secrets)," $MAINFLUX_DIR/docker/.env
sed -i "s,MF_VAULT_TOKEN=.*,MF_VAULT_TOKEN=$(awk -F ": " '$1 == "Initial Root Token" {print $2}' data/secrets)," $MAINFLUX_DIR/docker/.env
}
vault() {
docker exec -it mainflux-vault vault "$@"
}
@@ -8,3 +17,5 @@ vault() {
mkdir -p data
vault operator init 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > data/secrets)
write_env
+1 -1
View File
@@ -8,7 +8,7 @@ cd $scriptdir
readDotEnv() {
set -o allexport
source $MAINFLUX_DIR/.env
source $MAINFLUX_DIR/docker/.env
set +o allexport
}
+1 -1
View File
@@ -6,7 +6,7 @@ export MAINFLUX_DIR=$scriptdir/../../../
readDotEnv() {
set -o allexport
source $MAINFLUX_DIR/.env
source $MAINFLUX_DIR/docker/.env
set +o allexport
}
+1 -1
View File
@@ -60,7 +60,7 @@ var (
// ErrMissingPolicyAct indicates missing policies action.
ErrMissingPolicyAct = errors.New("falmormed policy action")
// ErrMissingCertData indicates missing cert data (ttl, key_type or key_bits).
// ErrMissingCertData indicates missing cert data (ttl).
ErrMissingCertData = errors.New("missing certificate data")
// ErrInvalidTopic indicates an invalid subscription topic.
+33 -47
View File
@@ -4,10 +4,10 @@
package sdk
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/mainflux/mainflux/pkg/errors"
)
@@ -16,16 +16,16 @@ const certsEndpoint = "certs"
// Cert represents certs data.
type Cert struct {
CACert string `json:"issuing_ca,omitempty"`
ClientKey string `json:"client_key,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ThingID string `json:"thing_id,omitempty"`
CertSerial string `json:"cert_serial,omitempty"`
ClientKey string `json:"client_key,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
Expiration time.Time `json:"expiration,omitempty"`
}
func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, errors.SDKError) {
func (sdk mfSDK) IssueCert(thingID, valid, token string) (Cert, errors.SDKError) {
r := certReq{
ThingID: thingID,
KeyBits: keyBits,
KeyType: keyType,
Valid: valid,
}
d, err := json.Marshal(r)
@@ -34,63 +34,49 @@ func (sdk mfSDK) IssueCert(thingID string, keyBits int, keyType, valid, token st
}
url := fmt.Sprintf("%s/%s", sdk.certsURL, certsEndpoint)
resp, err := request(http.MethodPost, token, url, d)
if err != nil {
return Cert{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
if err := errors.CheckError(resp, http.StatusOK); err != nil {
return Cert{}, err
_, body, sdkerr := sdk.processRequest(http.MethodPost, url, token, string(CTJSON), d, http.StatusCreated)
if sdkerr != nil {
return Cert{}, sdkerr
}
var c Cert
if err := json.NewDecoder(resp.Body).Decode(&c); err != nil {
if err := json.Unmarshal(body, &c); err != nil {
return Cert{}, errors.NewSDKError(err)
}
return c, nil
}
func (sdk mfSDK) RemoveCert(id, token string) errors.SDKError {
resp, err := request(http.MethodDelete, token, fmt.Sprintf("%s/%s", sdk.certsURL, id), nil)
if resp != nil {
resp.Body.Close()
}
func (sdk mfSDK) ViewCert(id, token string) (Cert, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.certsURL, certsEndpoint, id)
_, body, err := sdk.processRequest(http.MethodGet, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return errors.NewSDKError(err)
return Cert{}, err
}
switch resp.StatusCode {
case http.StatusForbidden:
return errors.NewSDKError(errors.ErrAuthorization)
default:
return errors.CheckError(resp, http.StatusNoContent)
var cert Cert
if err := json.Unmarshal(body, &cert); err != nil {
return Cert{}, errors.NewSDKError(err)
}
return cert, nil
}
func (sdk mfSDK) RevokeCert(thingID, certID, token string) errors.SDKError {
panic("not implemented")
}
func request(method, jwt, url string, data []byte) (*http.Response, errors.SDKError) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
func (sdk mfSDK) RevokeCert(id, token string) (time.Time, errors.SDKError) {
url := fmt.Sprintf("%s/%s/%s", sdk.certsURL, certsEndpoint, id)
_, body, err := sdk.processRequest(http.MethodDelete, url, token, string(CTJSON), nil, http.StatusOK)
if err != nil {
return nil, errors.NewSDKError(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", jwt)
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
return nil, errors.NewSDKError(err)
return time.Time{}, err
}
return res, nil
var rcr revokeCertsRes
if err := json.Unmarshal(body, &rcr); err != nil {
return time.Time{}, errors.NewSDKError(err)
}
return rcr.RevocationTime, nil
}
type certReq struct {
ThingID string `json:"thing_id"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
Encryption string `json:"encryption"`
Valid string `json:"valid"`
ThingID string `json:"thing_id"`
Valid string `json:"ttl"`
}
+4
View File
@@ -99,3 +99,7 @@ func (res retrieveKeyRes) Headers() map[string]string {
func (res retrieveKeyRes) Empty() bool {
return false
}
type revokeCertsRes struct {
RevocationTime time.Time `json:"revocation_time"`
}
+5 -5
View File
@@ -236,13 +236,13 @@ type SDK interface {
Whitelist(cfg BootstrapConfig, token string) errors.SDKError
// IssueCert issues a certificate for a thing required for mtls.
IssueCert(thingID string, keyBits int, keyType, valid, token string) (Cert, errors.SDKError)
IssueCert(thingID, valid, token string) (Cert, errors.SDKError)
// RemoveCert removes a certificate
RemoveCert(id, token string) errors.SDKError
// ViewCert returns a certificate given certificate ID
ViewCert(certID, token string) (Cert, errors.SDKError)
// RevokeCert revokes certificate with certID for thing with thingID
RevokeCert(thingID, certID, token string) errors.SDKError
// RevokeCert revokes certificate for thing with thingID
RevokeCert(thingID, token string) (time.Time, errors.SDKError)
// Issue issues a new key, returning its token value alongside.
Issue(duration time.Duration, token string) (KeyRes, errors.SDKError)
+1 -1
View File
@@ -162,7 +162,7 @@ Provision service has `/certs` endpoint that can be used to generate certificate
- `users_token` - users authentication token or API token
- `thing_id` - id of the thing for which certificate is going to be generated
```bash
curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer <users_token>" -H 'Content-Type: application/json' -d '{"thing_id": "<thing_id>", "key_bits":4096, "ttl":"2400h" }'
curl -s -X POST http://localhost:8190/certs -H "Authorization: Bearer <users_token>" -H 'Content-Type: application/json' -d '{"thing_id": "<thing_id>", "ttl":"2400h" }'
```
```json
{
+2 -2
View File
@@ -38,7 +38,7 @@ func (lm *loggingMiddleware) Provision(token, name, externalID, externalKey stri
return lm.svc.Provision(token, name, externalID, externalKey)
}
func (lm *loggingMiddleware) Cert(token, thingID, duration string, keyBits int) (cert string, key string, err error) {
func (lm *loggingMiddleware) Cert(token, thingID, duration string) (cert string, key string, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method cert for token: %s and thing: %v took %s to complete", token, thingID, time.Since(begin))
if err != nil {
@@ -48,7 +48,7 @@ func (lm *loggingMiddleware) Cert(token, thingID, duration string, keyBits int)
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.Cert(token, thingID, duration, keyBits)
return lm.svc.Cert(token, thingID, duration)
}
func (lm *loggingMiddleware) Mapping(token string) (res map[string]interface{}, err error) {
+1 -3
View File
@@ -51,9 +51,7 @@ type Gateway struct {
// Cert represetns the certificate config
type Cert struct {
TTL string `json:"ttl" toml:"ttl"`
KeyBits int `json:"key_bits" toml:"key_bits"`
KeyType string `json:"key_type"`
TTL string `json:"ttl" toml:"ttl"`
}
// Config struct of Provision
+8 -8
View File
@@ -61,8 +61,7 @@ type Service interface {
// A duration string is a possibly signed sequence of decimal numbers,
// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// keyBits for certificate key
Cert(token, thingID, duration string, keyBits int) (string, string, error)
Cert(token, thingID, duration string) (string, string, error)
}
type provisionService struct {
@@ -216,7 +215,7 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin
if ps.conf.Bootstrap.X509Provision {
var cert SDK.Cert
cert, err = ps.sdk.IssueCert(thing.ID, ps.conf.Cert.KeyBits, ps.conf.Cert.KeyType, ps.conf.Cert.TTL, token)
cert, err = ps.sdk.IssueCert(thing.ID, ps.conf.Cert.TTL, token)
if err != nil {
e := errors.Wrap(err, fmt.Errorf("thing id: %s", thing.ID))
return res, errors.Wrap(ErrFailedCertCreation, e)
@@ -224,10 +223,10 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin
res.ClientCert[thing.ID] = cert.ClientCert
res.ClientKey[thing.ID] = cert.ClientKey
res.CACert = cert.CACert
res.CACert = ""
if needsBootstrap(thing) {
if err = ps.sdk.UpdateBootstrapCerts(bsConfig.MFThing, cert.ClientCert, cert.ClientKey, cert.CACert, token); err != nil {
if err = ps.sdk.UpdateBootstrapCerts(bsConfig.MFThing, cert.ClientCert, cert.ClientKey, "", token); err != nil {
return Result{}, errors.Wrap(ErrFailedCertCreation, err)
}
}
@@ -253,7 +252,7 @@ func (ps *provisionService) Provision(token, name, externalID, externalKey strin
return res, nil
}
func (ps *provisionService) Cert(token, thingID, ttl string, keyBits int) (string, string, error) {
func (ps *provisionService) Cert(token, thingID, ttl string) (string, string, error) {
token, err := ps.createTokenIfEmpty(token)
if err != nil {
return "", "", err
@@ -263,7 +262,7 @@ func (ps *provisionService) Cert(token, thingID, ttl string, keyBits int) (strin
if err != nil {
return "", "", errors.Wrap(ErrUnauthorized, err)
}
cert, err := ps.sdk.IssueCert(th.ID, ps.conf.Cert.KeyBits, ps.conf.Cert.KeyType, ps.conf.Cert.TTL, token)
cert, err := ps.sdk.IssueCert(th.ID, ps.conf.Cert.TTL, token)
return cert.ClientCert, cert.ClientKey, err
}
@@ -388,7 +387,8 @@ func (ps *provisionService) recover(e *error, ths *[]SDK.Thing, chs *[]SDK.Chann
clean(ps, things, channels, token)
for _, th := range things {
if ps.conf.Bootstrap.X509Provision && needsBootstrap(th) {
ps.errLog(ps.sdk.RemoveCert(th.ID, token))
_, err := ps.sdk.RevokeCert(th.ID, token)
ps.errLog(err)
}
if needsBootstrap(th) {
bs, err := ps.sdk.ViewBootstrap(th.ID, token)