Mainflux.mainflux/certs/pki/vault.go

165 lines
4.0 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package pki wraps vault client
package pki
import (
"encoding/json"
"time"
"github.com/hashicorp/vault/api"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mitchellh/mapstructure"
)
const (
issue = "issue"
cert = "cert"
revoke = "revoke"
)
var (
// ErrMissingCACertificate indicates missing CA certificate.
ErrMissingCACertificate = errors.New("missing CA certificate for certificate signing")
// ErrFailedCertCreation indicates failed to certificate creation.
ErrFailedCertCreation = errors.New("failed to create client certificate")
// ErrFailedCertRevocation indicates failed certificate revocation.
ErrFailedCertRevocation = errors.New("failed to revoke certificate")
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 int64 `json:"expire" mapstructure:"expiration"`
}
// Agent represents the Vault PKI interface.
type Agent interface {
// IssueCert issues certificate on PKI
IssueCert(cn, ttl string) (Cert, error)
// Read retrieves certificate from PKI
Read(serial string) (Cert, error)
// Revoke revokes certificate from PKI
Revoke(serial string) (time.Time, error)
}
type pkiAgent struct {
token string
path string
role string
host string
issueURL string
readURL string
revokeURL string
client *api.Client
}
type certReq struct {
CommonName string `json:"common_name"`
TTL string `json:"ttl"`
}
type certRevokeReq struct {
SerialNumber string `json:"serial_number"`
}
// NewVaultClient instantiates a Vault client.
func NewVaultClient(token, host, path, role string) (Agent, error) {
conf := api.DefaultConfig()
conf.Address = host
client, err := api.NewClient(conf)
if err != nil {
return nil, err
}
client.SetToken(token)
p := pkiAgent{
token: token,
host: host,
role: role,
path: path,
client: client,
issueURL: "/" + path + "/" + issue + "/" + role,
readURL: "/" + path + "/" + cert + "/",
revokeURL: "/" + path + "/" + revoke,
}
return &p, nil
}
func (p *pkiAgent) IssueCert(cn, ttl string) (Cert, error) {
cReq := certReq{
CommonName: cn,
TTL: ttl,
}
var certIssueReq map[string]interface{}
data, err := json.Marshal(cReq)
if err != nil {
return Cert{}, err
}
if err := json.Unmarshal(data, &certIssueReq); err != nil {
return Cert{}, nil
}
s, err := p.client.Logical().Write(p.issueURL, certIssueReq)
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
}
func (p *pkiAgent) Read(serial string) (Cert, error) {
s, err := p.client.Logical().Read(p.readURL + serial)
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
}
func (p *pkiAgent) Revoke(serial string) (time.Time, error) {
cReq := certRevokeReq{
SerialNumber: serial,
}
var certRevokeReq map[string]interface{}
data, err := json.Marshal(cReq)
if err != nil {
return time.Time{}, err
}
if err := json.Unmarshal(data, &certRevokeReq); err != nil {
return time.Time{}, nil
}
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.Second)), nil
}